Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd4bc7590e | |||
| d626fed76a | |||
| 739af6ecb4 | |||
| 56b740ef5c | |||
| 18e70c52e1 | |||
| cc3ccf0ddd | |||
| 38db12c2b0 | |||
| a749611370 | |||
| b662d185ca | |||
| 6de6933819 | |||
| f46db48399 | |||
| 8049ddfe26 | |||
| f64e3dea51 | |||
| 17455afe4d | |||
| 047ce47742 | |||
| 558402848e | |||
| db93395061 | |||
| e246e98b20 | |||
| 7ecfa4f384 | |||
| bc216fb7d1 | |||
| b92528aeba | |||
| 81e2a53692 | |||
| 602248425a | |||
| 3b802c08b7 | |||
| 972b30ddc2 | |||
| f58b49fa08 | |||
| 131f9ea645 | |||
| cbf05f84fe | |||
| 611da86353 | |||
| fb4ac5ae51 | |||
| 063c7de783 | |||
| 08d37e0deb | |||
| ff30e5461f | |||
| 4071291fa9 | |||
| 611effd83b | |||
| b63d197292 | |||
| cf901ddac5 | |||
| 122985bd67 | |||
| 19c8482236 | |||
| 33a1bd2337 | |||
| 5f8cb96bab | |||
| 6ddfaa86ae | |||
| 4dc7c0da7a | |||
| 34a1b68d79 | |||
| a04edfe80f | |||
| 32c1abd5d9 | |||
| a47be21f9f | |||
| 5e0ff7d855 | |||
| c3acdc2e38 | |||
| 13c99bcf05 | |||
| b4e0b534d5 | |||
| be4119f84f | |||
| 32b0567343 | |||
| 82fe329129 | |||
| 0e5c3ecfda | |||
| 53f1f1989e | |||
| 081046b6cd | |||
| a61ae52610 | |||
| 477dce6cf8 | |||
| 8a0a47223d | |||
| 1f798214de | |||
| 4459824cc8 | |||
| c2a40d6900 | |||
| a7fa97f0e1 | |||
| 1190fe1204 | |||
| 7026e26d68 | |||
| 37e40a78f2 | |||
| edde5cd0ff | |||
| bff3c373b3 | |||
| f30ad05c70 | |||
| 354dd6b0a2 | |||
| e06de83295 | |||
| ab389d4e71 | |||
| eee946d2cf | |||
| 73af324a3a | |||
| 832118e61a | |||
| 264f8fdd7f | |||
| 36cb8f4676 | |||
| 36fcd5784f | |||
| 46450577c2 | |||
| d3ba23fa64 | |||
| 6d27b5d321 | |||
| 888e75a8fb | |||
| b7f255ce0b | |||
| 477947ba96 | |||
| 33e9a21ce4 | |||
| 2d078e8cd8 | |||
| d6d960dbe5 | |||
| ed109d7ec8 | |||
| bc917863e0 | |||
| e964c1edff | |||
| f5e42ff6f4 | |||
| f594d70daf | |||
| d0608a63b6 | |||
| 09f3ec7125 | |||
| 95593ac4bf | |||
| 1eecc7b17a | |||
| b05b773bd7 | |||
| debc926612 | |||
| 70eb75a3e6 | |||
| 903198a724 | |||
| 7b3dd8a6f5 | |||
| 208f83b9a2 | |||
| 4e3388964a | |||
| b9e1295f7a | |||
| d3865688c8 | |||
| 5a726c2e4d | |||
| db6a7e3e28 | |||
| d9aedfe7d3 | |||
| b2fe57c950 | |||
| 805026360e | |||
| 3d37d21b5f | |||
| c33f0b52e7 | |||
| 1d795c0643 | |||
| c00c0eed1a | |||
| 47fb367486 | |||
| 5e16b38524 | |||
| c728f43c63 | |||
| d1458d0063 | |||
| 66d772a6bc | |||
| a670af376e | |||
| 16691395e0 | |||
| 3018cf8c1a | |||
| 6a126405bd | |||
| ee2f269c06 | |||
| 780cb8a9e6 | |||
| 388d9573fd | |||
| 578dba1987 | |||
| 124c9a7d88 | |||
| e53fa10397 | |||
| 1295668abb | |||
| 880257a431 | |||
| 8b3125bcf3 | |||
| 471b504799 | |||
| b713acbeff | |||
| ff736e9ad3 | |||
| 384c2772fb | |||
| e6d8d8f242 | |||
| ca8efbf657 | |||
| eb6d4e183b | |||
| 6d9deda9c5 | |||
| 96890fab97 | |||
| c0d23e27e4 | |||
| 2d2798954e | |||
| 44e5fa0b82 | |||
| 82cb31f3b6 | |||
| 9348dd5208 |
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "include/web-socket-js-project"]
|
||||||
|
path = include/web-socket-js-project
|
||||||
|
url = https://github.com/gimite/web-socket-js.git
|
||||||
+62
-2
@@ -1,8 +1,68 @@
|
|||||||
Changes
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
0.1.0
|
0.6.1 - May 11, 2015
|
||||||
-----------------
|
--------------------
|
||||||
|
|
||||||
|
* **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
|
||||||
|
|
||||||
|
0.6.0 - Feb 18, 2014
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* **NOTE** : 0.6.0 will break existing code that sub-classes WebsocketProxy
|
||||||
|
* Refactor to use standard SocketServer RequestHandler design
|
||||||
|
* Fix zombie process bug on certain systems when using multiprocessing
|
||||||
|
* Add better unit tests
|
||||||
|
* Log information via python `logging` module
|
||||||
|
|
||||||
|
0.5.1 - Jun 27, 2013
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* use upstream einaros/ws (>=0.4.27) with websockify.js
|
||||||
|
* file_only and no_parent security options for WSRequestHandler
|
||||||
|
* Update build of web-socket-js (c0855c6cae)
|
||||||
|
* add include/web-socket-js-project submodule to gimite/web-socket-js
|
||||||
|
for DSFG compliance.
|
||||||
|
* drop Hixie protocol support
|
||||||
|
|
||||||
|
0.4.1 - Mar 12, 2013
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* ***NOTE*** : 0.5.0 will drop Hixie protocol support
|
||||||
|
* add include/ directory and remove some dev files from source
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
0.4.0 - Mar 12, 2013
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* ***NOTE*** : 0.5.0 will drop Hixie protocol support
|
||||||
|
* use Buffer base64 support in Node.js implementation
|
||||||
|
|
||||||
|
0.3.0 - Jan 15, 2013
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* refactor into modules: websocket, websocketproxy
|
||||||
|
* switch to web-socket-js that uses IETF 6455
|
||||||
|
* change to MPL 2.0 license for include/*.js
|
||||||
|
* fix session recording
|
||||||
|
|
||||||
|
0.2.1 - Oct 15, 2012
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* re-released with updated version number
|
||||||
|
|
||||||
|
0.2.0 - Sep 17, 2012
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* Binary data support in websock.js
|
||||||
|
* Target config file/dir and multiple targets with token selector
|
||||||
|
* IPv6 fixes
|
||||||
|
* SSL target support
|
||||||
|
* Proxy to/from unix socket
|
||||||
|
|
||||||
|
|
||||||
|
0.1.0 - May 11, 2012
|
||||||
|
--------------------
|
||||||
|
|
||||||
* Initial versioned release.
|
* Initial versioned release.
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -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
@@ -1 +1,2 @@
|
|||||||
include CHANGES.txt *.py README.md LICENSE.txt
|
include CHANGES.txt README.md LICENSE.txt
|
||||||
|
graft include
|
||||||
|
|||||||
@@ -8,14 +8,42 @@ to normal socket traffic. Websockify accepts the WebSockets handshake,
|
|||||||
parses it, and then begins forwarding traffic between the client and
|
parses it, and then begins forwarding traffic between the client and
|
||||||
the target in both directions.
|
the target in both directions.
|
||||||
|
|
||||||
|
### News/help/contact
|
||||||
|
|
||||||
|
Notable commits, announcements and news are posted to
|
||||||
|
<a href="http://www.twitter.com/noVNC">@noVNC</a>
|
||||||
|
|
||||||
|
If you are a websockify developer/integrator/user (or want to be)
|
||||||
|
please join the <a
|
||||||
|
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC/websockify
|
||||||
|
discussion group</a>
|
||||||
|
|
||||||
|
Bugs and feature requests can be submitted via [github
|
||||||
|
issues](https://github.com/kanaka/websockify/issues).
|
||||||
|
|
||||||
|
If you want to show appreciation for websockify you could donate to a great
|
||||||
|
non-profits such as: [Compassion
|
||||||
|
International](http://www.compassion.com/), [SIL](http://www.sil.org),
|
||||||
|
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
|
||||||
|
Foundation](https://www.eff.org/), [Against Malaria
|
||||||
|
Foundation](http://www.againstmalaria.com/), [Nothing But
|
||||||
|
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
|
||||||
|
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
|
||||||
|
|
||||||
### WebSockets binary data
|
### 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://)
|
||||||
|
|
||||||
@@ -110,7 +138,7 @@ new (moved) port of the program.
|
|||||||
The program wrap mode is invoked by replacing the target with `--`
|
The program wrap mode is invoked by replacing the target with `--`
|
||||||
followed by the program command line to wrap.
|
followed by the program command line to wrap.
|
||||||
|
|
||||||
`./websockify 2023 -- PROGRAM ARGS`
|
`./run 2023 -- PROGRAM ARGS`
|
||||||
|
|
||||||
The `--wrap-mode` option can be used to indicate what action to take
|
The `--wrap-mode` option can be used to indicate what action to take
|
||||||
when the wrapped program exits or daemonizes.
|
when the wrapped program exits or daemonizes.
|
||||||
@@ -119,16 +147,17 @@ Here is an example of using websockify to wrap the vncserver command
|
|||||||
(which backgrounds itself) for use with
|
(which backgrounds itself) for use with
|
||||||
[noVNC](https://github.com/kanaka/noVNC):
|
[noVNC](https://github.com/kanaka/noVNC):
|
||||||
|
|
||||||
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
`./run 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
||||||
|
|
||||||
Here is an example of wrapping telnetd (from krb5-telnetd).telnetd
|
Here is an example of wrapping telnetd (from krb5-telnetd). telnetd
|
||||||
exits after the connection closes so the wrap mode is set to respawn
|
exits after the connection closes so the wrap mode is set to respawn
|
||||||
the command:
|
the command:
|
||||||
|
|
||||||
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
`sudo ./run 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
||||||
|
|
||||||
The `wstelnet.html` page demonstrates a simple WebSockets based telnet
|
The `wstelnet.html` page demonstrates a simple WebSockets based telnet
|
||||||
client.
|
client (use 'localhost' and '2023' for the host and port
|
||||||
|
respectively).
|
||||||
|
|
||||||
|
|
||||||
### Building the Python ssl module (for python 2.5 and older)
|
### Building the Python ssl module (for python 2.5 and older)
|
||||||
@@ -137,11 +166,10 @@ client.
|
|||||||
|
|
||||||
`sudo aptitude install python-dev bluetooth-dev`
|
`sudo aptitude install python-dev bluetooth-dev`
|
||||||
|
|
||||||
* Download, build the ssl module and symlink to it:
|
* At the top level of the websockify repostory, download, build and
|
||||||
|
symlink the ssl module:
|
||||||
|
|
||||||
`cd websockify/`
|
`wget --no-check-certificate http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
|
||||||
|
|
||||||
`wget http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
|
|
||||||
|
|
||||||
`tar xvzf ssl-1.15.tar.gz`
|
`tar xvzf ssl-1.15.tar.gz`
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Installation
|
|||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
|
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
|
||||||
https://github.com/kanaka/noVNC/wiki/Compiling-Websockify-to-Windows-Executable
|
https://github.com/kanaka/websockify/wiki/Compiling-Websockify-as-Windows-Executable
|
||||||
|
|
||||||
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
|
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
Submodule
+1
Submodule include/web-socket-js-project added at c0855c6cae
Binary file not shown.
@@ -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
@@ -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
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
|
||||||
|
"name": "websockify",
|
||||||
|
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
|
||||||
|
"license": "LGPL-3",
|
||||||
|
"version": "0.6.1",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/kanaka/websockify.git"
|
||||||
|
},
|
||||||
|
"files": ["../../docs/LICENSE.LGPL-3"],
|
||||||
|
"bin": {
|
||||||
|
"websockify": "./websockify.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.9"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ws": ">=0.4.27",
|
||||||
|
"base64": "latest",
|
||||||
|
"optimist": "latest",
|
||||||
|
"policyfile": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
Regular → Executable
+76
-24
@@ -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
@@ -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
@@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-23
@@ -9,11 +9,22 @@
|
|||||||
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
||||||
|
|
||||||
require 'gserver'
|
require 'gserver'
|
||||||
|
require 'openssl'
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
require 'digest/md5'
|
require 'digest/md5'
|
||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
require 'base64'
|
require 'base64'
|
||||||
|
|
||||||
|
unless OpenSSL::SSL::SSLSocket.instance_methods.index("read_nonblock")
|
||||||
|
module OpenSSL
|
||||||
|
module SSL
|
||||||
|
class SSLSocket
|
||||||
|
alias :read_nonblock :readpartial
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class EClose < Exception
|
class EClose < Exception
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -44,6 +55,16 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
host = opts['listen_host'] || GServer::DEFAULT_HOST
|
host = opts['listen_host'] || GServer::DEFAULT_HOST
|
||||||
|
|
||||||
super(port, host)
|
super(port, host)
|
||||||
|
msg opts.inspect
|
||||||
|
if opts['server_cert']
|
||||||
|
msg "creating ssl context"
|
||||||
|
@sslContext = OpenSSL::SSL::SSLContext.new
|
||||||
|
@sslContext.cert = OpenSSL::X509::Certificate.new(File.open(opts['server_cert']))
|
||||||
|
@sslContext.key = OpenSSL::PKey::RSA.new(File.open(opts['server_key']))
|
||||||
|
@sslContext.ca_file = opts['server_cert']
|
||||||
|
@sslContext.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
@sslContext.verify_depth = 0
|
||||||
|
end
|
||||||
|
|
||||||
@@client_id = 0 # Track client number total on class
|
@@client_id = 0 # Track client number total on class
|
||||||
|
|
||||||
@@ -53,6 +74,18 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
def serve(io)
|
def serve(io)
|
||||||
@@client_id += 1
|
@@client_id += 1
|
||||||
|
msg self.inspect
|
||||||
|
if @sslContext
|
||||||
|
msg "Enabling SSL context"
|
||||||
|
ssl = OpenSSL::SSL::SSLSocket.new(io, @sslContext)
|
||||||
|
#ssl.sync_close = true
|
||||||
|
#ssl.sync = true
|
||||||
|
msg "SSL accepting"
|
||||||
|
ssl.accept
|
||||||
|
io = ssl # replace the unencrypted handle with the encrypted one
|
||||||
|
end
|
||||||
|
|
||||||
|
msg "initializing thread"
|
||||||
|
|
||||||
# Initialize per thread state
|
# Initialize per thread state
|
||||||
t = Thread.current
|
t = Thread.current
|
||||||
@@ -61,11 +94,11 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
t[:recv_part] = nil
|
t[:recv_part] = nil
|
||||||
t[:base64] = nil
|
t[:base64] = nil
|
||||||
|
|
||||||
puts "in serve, client: #{t[:client].inspect}"
|
puts "in serve, client: #{t[:my_client_id].inspect}"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
t[:client] = do_handshake(io)
|
t[:client] = do_handshake(io)
|
||||||
new_client(t[:client])
|
new_websocket_client(t[:client])
|
||||||
rescue EClose => e
|
rescue EClose => e
|
||||||
msg "Client closed: #{e.message}"
|
msg "Client closed: #{e.message}"
|
||||||
return
|
return
|
||||||
@@ -85,12 +118,12 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
if @verbose then print token; STDOUT.flush; end
|
if @verbose then print token; STDOUT.flush; end
|
||||||
end
|
end
|
||||||
|
|
||||||
def msg(msg)
|
def msg(m)
|
||||||
puts "% 3d: %s" % [Thread.current[:my_client_id], msg]
|
printf("% 3d: %s\n", Thread.current[:my_client_id] || 0, m)
|
||||||
end
|
end
|
||||||
|
|
||||||
def vmsg(msg)
|
def vmsg(m)
|
||||||
if @verbose then msg(msg) end
|
if @verbose then msg(m) end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -110,12 +143,11 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
def unmask(buf, hlen, length)
|
def unmask(buf, hlen, length)
|
||||||
pstart = hlen + 4
|
pstart = hlen + 4
|
||||||
mask = buf[hlen...hlen+4]
|
mask = buf[hlen...hlen+4].each_byte.map{|b|b}
|
||||||
data = buf[pstart...pstart+length]
|
data = buf[pstart...pstart+length]
|
||||||
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
|
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
|
||||||
for i in (0...data.length) do
|
i=-1
|
||||||
data[i] ^= mask[i%4]
|
data = data.each_byte.map{|b| i+=1; (b ^ mask[i % 4]).chr}.join("")
|
||||||
end
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -237,7 +269,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
while t[:send_parts].length > 0
|
while t[:send_parts].length > 0
|
||||||
buf = t[:send_parts].shift
|
buf = t[:send_parts].shift
|
||||||
sent = t[:client].send(buf, 0)
|
sent = t[:client].write(buf)
|
||||||
|
|
||||||
if sent == buf.length
|
if sent == buf.length
|
||||||
traffic "<"
|
traffic "<"
|
||||||
@@ -257,7 +289,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
closed = false
|
closed = false
|
||||||
bufs = []
|
bufs = []
|
||||||
|
|
||||||
buf = t[:client].recv(@@Buffer_size)
|
buf = t[:client].read_nonblock(@@Buffer_size)
|
||||||
|
|
||||||
if buf.length == 0
|
if buf.length == 0
|
||||||
return bufs, "Client closed abrubtly"
|
return bufs, "Client closed abrubtly"
|
||||||
@@ -286,10 +318,10 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if buf[0...2] == "\xff\x00":
|
if buf[0...2] == "\xff\x00"
|
||||||
closed = "Client sent orderly close frame"
|
closed = "Client sent orderly close frame"
|
||||||
break
|
break
|
||||||
elsif buf[0...2] == "\x00\xff":
|
elsif buf[0...2] == "\x00\xff"
|
||||||
buf = buf[2...buf.length]
|
buf = buf[2...buf.length]
|
||||||
continue # No-op frame
|
continue # No-op frame
|
||||||
elsif buf.count("\xff") == 0
|
elsif buf.count("\xff") == 0
|
||||||
@@ -308,7 +340,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
bufs << frame['payload']
|
bufs << frame['payload']
|
||||||
|
|
||||||
if frame['left'] > 0:
|
if frame['left'] > 0
|
||||||
buf = buf[-frame['left']...buf.length]
|
buf = buf[-frame['left']...buf.length]
|
||||||
else
|
else
|
||||||
buf = ''
|
buf = ''
|
||||||
@@ -328,10 +360,10 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
end
|
end
|
||||||
|
|
||||||
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
|
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
|
||||||
t[:client].send(buf, 0)
|
t[:client].write(buf)
|
||||||
elsif t[:version] == "hixie-76"
|
elsif t[:version] == "hixie-76"
|
||||||
buf = "\xff\x00"
|
buf = "\xff\x00"
|
||||||
t[:client].send(buf, 0)
|
t[:client].write(buf)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -344,16 +376,21 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
raise EClose, "ignoring socket not ready"
|
raise EClose, "ignoring socket not ready"
|
||||||
end
|
end
|
||||||
|
|
||||||
handshake = sock.recv(1024, Socket::MSG_PEEK)
|
handshake = ""
|
||||||
#msg "Handshake [#{handshake.inspect}]"
|
msg "About to read from sock [#{sock.inspect}]"
|
||||||
|
handshake = sock.read_nonblock(1024)
|
||||||
|
msg "Handshake [#{handshake.inspect}]"
|
||||||
|
|
||||||
if handshake == ""
|
if handshake == nil or handshake == ""
|
||||||
raise(EClose, "ignoring empty handshake")
|
raise(EClose, "ignoring empty handshake")
|
||||||
else
|
else
|
||||||
stype = "Plain non-SSL (ws://)"
|
stype = "Plain non-SSL (ws://)"
|
||||||
scheme = "ws"
|
scheme = "ws"
|
||||||
|
if sock.class == OpenSSL::SSL::SSLSocket
|
||||||
|
stype = "SSL (wss://)"
|
||||||
|
scheme = "wss"
|
||||||
|
end
|
||||||
retsock = sock
|
retsock = sock
|
||||||
sock.recv(1024)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
h = t[:headers] = {}
|
h = t[:headers] = {}
|
||||||
@@ -365,7 +402,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
|
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
|
||||||
h[hsplit[1].strip.downcase] = hsplit[2]
|
h[hsplit[1].strip.downcase] = hsplit[2]
|
||||||
end
|
end
|
||||||
#puts "Headers: #{h.inspect}"
|
puts "Headers: #{h.inspect}"
|
||||||
|
|
||||||
unless h.has_key?('upgrade') &&
|
unless h.has_key?('upgrade') &&
|
||||||
h['upgrade'].downcase == 'websocket'
|
h['upgrade'].downcase == 'websocket'
|
||||||
@@ -445,7 +482,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
if t[:path] then msg "Path: '%s'" % [t[:path]] end
|
if t[:path] then msg "Path: '%s'" % [t[:path]] end
|
||||||
|
|
||||||
#puts "sending reponse #{response.inspect}"
|
#puts "sending reponse #{response.inspect}"
|
||||||
retsock.send(response, 0)
|
retsock.write(response)
|
||||||
|
|
||||||
# Return the WebSocket socket which may be SSL wrapped
|
# Return the WebSocket socket which may be SSL wrapped
|
||||||
return retsock
|
return retsock
|
||||||
|
|||||||
+3
-3
@@ -39,7 +39,7 @@ Traffic Legend:
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Echo back whatever is received
|
# Echo back whatever is received
|
||||||
def new_client(client)
|
def new_websocket_client(client)
|
||||||
|
|
||||||
msg "connecting to: %s:%s" % [@target_host, @target_port]
|
msg "connecting to: %s:%s" % [@target_host, @target_port]
|
||||||
tsock = TCPSocket.open(@target_host, @target_port)
|
tsock = TCPSocket.open(@target_host, @target_port)
|
||||||
@@ -92,7 +92,7 @@ Traffic Legend:
|
|||||||
# Receive target data and queue for the client
|
# Receive target data and queue for the client
|
||||||
if ins && ins.include?(target)
|
if ins && ins.include?(target)
|
||||||
buf = target.recv(@@Buffer_size)
|
buf = target.recv(@@Buffer_size)
|
||||||
if buf.length == 0:
|
if buf.length == 0
|
||||||
raise EClose, "Target closed"
|
raise EClose, "Target closed"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ parser = OptionParser.new do |o|
|
|||||||
o.parse!
|
o.parse!
|
||||||
end
|
end
|
||||||
|
|
||||||
if ARGV.length < 2:
|
if ARGV.length < 2
|
||||||
puts "Too few arguments"
|
puts "Too few arguments"
|
||||||
exit 2
|
exit 2
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import websockify
|
||||||
|
|
||||||
|
websockify.websocketproxy.websockify_init()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = '0.1.0'
|
version = '0.6.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',
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-11
@@ -10,17 +10,17 @@ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
|||||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os, sys, select, optparse
|
import os, sys, select, optparse, logging
|
||||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||||
from websocket import WebSocketServer
|
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
||||||
|
|
||||||
class WebSocketEcho(WebSocketServer):
|
class WebSocketEcho(WebSocketRequestHandler):
|
||||||
"""
|
"""
|
||||||
WebSockets server that echos back whatever is received from the
|
WebSockets server that echos back whatever is received from the
|
||||||
client. """
|
client. """
|
||||||
buffer_size = 8096
|
buffer_size = 8096
|
||||||
|
|
||||||
def new_client(self):
|
def new_websocket_client(self):
|
||||||
"""
|
"""
|
||||||
Echo back whatever is received.
|
Echo back whatever is received.
|
||||||
"""
|
"""
|
||||||
@@ -28,28 +28,27 @@ class WebSocketEcho(WebSocketServer):
|
|||||||
cqueue = []
|
cqueue = []
|
||||||
c_pend = 0
|
c_pend = 0
|
||||||
cpartial = ""
|
cpartial = ""
|
||||||
rlist = [self.client]
|
rlist = [self.request]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
wlist = []
|
wlist = []
|
||||||
|
|
||||||
if cqueue or c_pend: wlist.append(self.client)
|
if cqueue or c_pend: wlist.append(self.request)
|
||||||
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
||||||
if excepts: raise Exception("Socket exception")
|
if excepts: raise Exception("Socket exception")
|
||||||
|
|
||||||
if self.client in outs:
|
if self.request in outs:
|
||||||
# Send queued target data to the client
|
# Send queued target data to the client
|
||||||
c_pend = self.send_frames(cqueue)
|
c_pend = self.send_frames(cqueue)
|
||||||
cqueue = []
|
cqueue = []
|
||||||
|
|
||||||
if self.client in ins:
|
if self.request in ins:
|
||||||
# Receive client data, decode it, and send it back
|
# Receive client data, decode it, and send it back
|
||||||
frames, closed = self.recv_frames()
|
frames, closed = self.recv_frames()
|
||||||
cqueue.extend(frames)
|
cqueue.extend(frames)
|
||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
self.send_close()
|
self.send_close()
|
||||||
raise self.EClose(closed)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
||||||
@@ -69,7 +68,9 @@ if __name__ == '__main__':
|
|||||||
except:
|
except:
|
||||||
parser.error("Invalid arguments")
|
parser.error("Invalid arguments")
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
opts.web = "."
|
opts.web = "."
|
||||||
server = WebSocketEcho(**opts.__dict__)
|
server = WebSocketServer(WebSocketEcho, **opts.__dict__)
|
||||||
server.start_server()
|
server.start_server()
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -12,7 +12,7 @@ require 'websocket'
|
|||||||
class WebSocketEcho < WebSocketServer
|
class WebSocketEcho < WebSocketServer
|
||||||
|
|
||||||
# Echo back whatever is received
|
# Echo back whatever is received
|
||||||
def new_client(client)
|
def new_websocket_client(client)
|
||||||
|
|
||||||
cqueue = []
|
cqueue = []
|
||||||
c_pend = 0
|
c_pend = 0
|
||||||
@@ -50,10 +50,17 @@ class WebSocketEcho < WebSocketServer
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
port = ARGV[0].to_i
|
port = ARGV[0].to_i || 8080
|
||||||
puts "Starting server on port #{port}"
|
puts "Starting server on port #{port}"
|
||||||
|
server_cert = nil
|
||||||
|
server_key = nil
|
||||||
|
if ARGV.length > 2
|
||||||
|
server_cert = ARGV[1]
|
||||||
|
server_key = ARGV[2]
|
||||||
|
end
|
||||||
|
|
||||||
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true)
|
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true,
|
||||||
|
'server_cert' => server_cert, 'server_key' => server_key)
|
||||||
server.start
|
server.start
|
||||||
server.join
|
server.join
|
||||||
|
|
||||||
|
|||||||
+18
-15
@@ -6,35 +6,37 @@ that has a random payload (length and content) that is checksummed and
|
|||||||
given a sequence number. Any errors are reported and counted.
|
given a sequence number. Any errors are reported and counted.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import sys, os, select, random, time, optparse
|
import sys, os, select, random, time, optparse, logging
|
||||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||||
from websocket import WebSocketServer
|
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
||||||
|
|
||||||
class WebSocketLoad(WebSocketServer):
|
class WebSocketLoadServer(WebSocketServer):
|
||||||
|
|
||||||
buffer_size = 65536
|
|
||||||
|
|
||||||
max_packet_size = 10000
|
|
||||||
recv_cnt = 0
|
recv_cnt = 0
|
||||||
send_cnt = 0
|
send_cnt = 0
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.errors = 0
|
|
||||||
self.delay = kwargs.pop('delay')
|
self.delay = kwargs.pop('delay')
|
||||||
|
|
||||||
|
WebSocketServer.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketLoad(WebSocketRequestHandler):
|
||||||
|
|
||||||
|
max_packet_size = 10000
|
||||||
|
|
||||||
|
def new_websocket_client(self):
|
||||||
print "Prepopulating random array"
|
print "Prepopulating random array"
|
||||||
self.rand_array = []
|
self.rand_array = []
|
||||||
for i in range(0, self.max_packet_size):
|
for i in range(0, self.max_packet_size):
|
||||||
self.rand_array.append(random.randint(0, 9))
|
self.rand_array.append(random.randint(0, 9))
|
||||||
|
|
||||||
WebSocketServer.__init__(self, *args, **kwargs)
|
self.errors = 0
|
||||||
|
|
||||||
def new_client(self):
|
|
||||||
self.send_cnt = 0
|
self.send_cnt = 0
|
||||||
self.recv_cnt = 0
|
self.recv_cnt = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.responder(self.client)
|
self.responder(self.request)
|
||||||
except:
|
except:
|
||||||
print "accumulated errors:", self.errors
|
print "accumulated errors:", self.errors
|
||||||
self.errors = 0
|
self.errors = 0
|
||||||
@@ -61,14 +63,13 @@ class WebSocketLoad(WebSocketServer):
|
|||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
self.send_close()
|
self.send_close()
|
||||||
raise self.EClose(closed)
|
|
||||||
|
|
||||||
now = time.time() * 1000
|
now = time.time() * 1000
|
||||||
if client in outs:
|
if client in outs:
|
||||||
if c_pend:
|
if c_pend:
|
||||||
last_send = now
|
last_send = now
|
||||||
c_pend = self.send_frames()
|
c_pend = self.send_frames()
|
||||||
elif now > (last_send + self.delay):
|
elif now > (last_send + self.server.delay):
|
||||||
last_send = now
|
last_send = now
|
||||||
c_pend = self.send_frames([self.generate()])
|
c_pend = self.send_frames([self.generate()])
|
||||||
|
|
||||||
@@ -161,7 +162,9 @@ if __name__ == '__main__':
|
|||||||
except:
|
except:
|
||||||
parser.error("Invalid arguments")
|
parser.error("Invalid arguments")
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
opts.web = "."
|
opts.web = "."
|
||||||
server = WebSocketLoad(**opts.__dict__)
|
server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__)
|
||||||
server.start_server()
|
server.start_server()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright(c)2013 NTT corp. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
""" Unit tests for websocket """
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import select
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import stubout
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from ssl import SSLError
|
||||||
|
from websockify import websocket as websocket
|
||||||
|
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||||
|
|
||||||
|
|
||||||
|
class MockConnection(object):
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def makefile(self, mode='r', bufsize=-1):
|
||||||
|
return open(self.path, mode, bufsize)
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def _init_logger(self, tmpdir):
|
||||||
|
name = 'websocket-unittest'
|
||||||
|
logger = logging.getLogger(name)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
logger.propagate = True
|
||||||
|
filename = "%s.log" % (name)
|
||||||
|
handler = logging.FileHandler(filename)
|
||||||
|
handler.setFormatter(logging.Formatter("%(message)s"))
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Called automatically before each test."""
|
||||||
|
super(WebSocketTestCase, self).setUp()
|
||||||
|
self.stubs = stubout.StubOutForTesting()
|
||||||
|
# Temporary dir for test data
|
||||||
|
self.tmpdir = tempfile.mkdtemp()
|
||||||
|
# Put log somewhere persistent
|
||||||
|
self._init_logger('./')
|
||||||
|
# Mock this out cause it screws tests up
|
||||||
|
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||||
|
self.server = self._get_websockserver(daemon=True,
|
||||||
|
ssl_only=False)
|
||||||
|
self.soc = self.server.socket('localhost')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Called automatically after each test."""
|
||||||
|
self.stubs.UnsetAll()
|
||||||
|
shutil.rmtree(self.tmpdir)
|
||||||
|
super(WebSocketTestCase, self).tearDown()
|
||||||
|
|
||||||
|
def _get_websockserver(self, **kwargs):
|
||||||
|
return websocket.WebSocketServer(listen_host='localhost',
|
||||||
|
listen_port=80,
|
||||||
|
key=self.tmpdir,
|
||||||
|
web=self.tmpdir,
|
||||||
|
record=self.tmpdir,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def _mock_os_open_oserror(self, file, flags):
|
||||||
|
raise OSError('')
|
||||||
|
|
||||||
|
def _mock_os_close_oserror(self, fd):
|
||||||
|
raise OSError('')
|
||||||
|
|
||||||
|
def _mock_os_close_oserror_EBADF(self, fd):
|
||||||
|
raise OSError(errno.EBADF, '')
|
||||||
|
|
||||||
|
def _mock_socket(self, *args, **kwargs):
|
||||||
|
return self.soc
|
||||||
|
|
||||||
|
def _mock_select(self, rlist, wlist, xlist, timeout=None):
|
||||||
|
return '_mock_select'
|
||||||
|
|
||||||
|
def _mock_select_exception(self, rlist, wlist, xlist, timeout=None):
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
def _mock_select_keyboardinterrupt(self, rlist, wlist,
|
||||||
|
xlist, timeout=None):
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
def _mock_select_systemexit(self, rlist, wlist, xlist, timeout=None):
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def test_daemonize_error(self):
|
||||||
|
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
|
||||||
|
self.stubs.Set(os, 'fork', lambda *args: None)
|
||||||
|
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||||
|
self.stubs.Set(os, 'close', self._mock_os_close_oserror)
|
||||||
|
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
|
||||||
|
|
||||||
|
def test_daemonize_EBADF_error(self):
|
||||||
|
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
|
||||||
|
self.stubs.Set(os, 'fork', lambda *args: None)
|
||||||
|
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||||
|
self.stubs.Set(os, 'close', self._mock_os_close_oserror_EBADF)
|
||||||
|
self.stubs.Set(os, 'open', self._mock_os_open_oserror)
|
||||||
|
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
|
||||||
|
|
||||||
|
def test_decode_hybi(self):
|
||||||
|
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||||
|
self.assertRaises(Exception, soc.decode_hybi, 'a' * 128,
|
||||||
|
base64=True)
|
||||||
|
|
||||||
|
def test_do_websocket_handshake(self):
|
||||||
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||||
|
soc.scheme = 'scheme'
|
||||||
|
headers = {'Sec-WebSocket-Protocol': 'binary',
|
||||||
|
'Sec-WebSocket-Version': '7',
|
||||||
|
'Sec-WebSocket-Key': 'foo'}
|
||||||
|
soc.do_websocket_handshake(headers, '127.0.0.1')
|
||||||
|
|
||||||
|
def test_do_handshake(self):
|
||||||
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||||
|
self.stubs.Set(select, 'select', self._mock_select)
|
||||||
|
self.stubs.Set(socket._socketobject, 'recv', lambda *args: 'mock_recv')
|
||||||
|
self.assertRaises(Exception, soc.do_handshake, self.soc, '127.0.0.1')
|
||||||
|
|
||||||
|
def test_do_handshake_ssl_error(self):
|
||||||
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||||
|
|
||||||
|
def _mock_wrap_socket(*args, **kwargs):
|
||||||
|
from ssl import SSLError
|
||||||
|
raise SSLError('unit test exception')
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', self._mock_select)
|
||||||
|
self.stubs.Set(socket._socketobject, 'recv', lambda *args: '\x16')
|
||||||
|
self.stubs.Set(ssl, 'wrap_socket', _mock_wrap_socket)
|
||||||
|
self.assertRaises(SSLError, soc.do_handshake, self.soc, '127.0.0.1')
|
||||||
|
|
||||||
|
def test_fallback_SIGCHILD(self):
|
||||||
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||||
|
soc.fallback_SIGCHLD(None, None)
|
||||||
|
|
||||||
|
def test_start_server_Exception(self):
|
||||||
|
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
|
lambda *args, **kwargs: None)
|
||||||
|
self.stubs.Set(select, 'select', self._mock_select_exception)
|
||||||
|
self.assertEqual(None, soc.start_server())
|
||||||
|
|
||||||
|
def test_start_server_KeyboardInterrupt(self):
|
||||||
|
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
|
lambda *args, **kwargs: None)
|
||||||
|
self.stubs.Set(select, 'select', self._mock_select_keyboardinterrupt)
|
||||||
|
self.assertEqual(None, soc.start_server())
|
||||||
|
|
||||||
|
def test_start_server_systemexit(self):
|
||||||
|
websocket.ssl = None
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
|
lambda *args, **kwargs: None)
|
||||||
|
self.stubs.Set(select, 'select', self._mock_select_systemexit)
|
||||||
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1,
|
||||||
|
verbose=True)
|
||||||
|
self.assertEqual(None, soc.start_server())
|
||||||
|
|
||||||
|
def test_WSRequestHandle_do_GET_nofile(self):
|
||||||
|
request = 'GET /tmp.txt HTTP/0.9'
|
||||||
|
with tempfile.NamedTemporaryFile() as test_file:
|
||||||
|
test_file.write(request)
|
||||||
|
test_file.flush()
|
||||||
|
test_file.seek(0)
|
||||||
|
con = MockConnection(test_file.name)
|
||||||
|
soc = websocket.WSRequestHandler(con, "127.0.0.1", file_only=True)
|
||||||
|
soc.path = ''
|
||||||
|
soc.headers = {'upgrade': ''}
|
||||||
|
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
||||||
|
lambda *args: None)
|
||||||
|
soc.do_GET()
|
||||||
|
self.assertEqual(404, soc.last_code)
|
||||||
|
|
||||||
|
def test_WSRequestHandle_do_GET_hidden_resource(self):
|
||||||
|
request = 'GET /tmp.txt HTTP/0.9'
|
||||||
|
with tempfile.NamedTemporaryFile() as test_file:
|
||||||
|
test_file.write(request)
|
||||||
|
test_file.flush()
|
||||||
|
test_file.seek(0)
|
||||||
|
con = MockConnection(test_file.name)
|
||||||
|
soc = websocket.WSRequestHandler(con, '127.0.0.1', no_parent=True)
|
||||||
|
soc.path = test_file.name + '?'
|
||||||
|
soc.headers = {'upgrade': ''}
|
||||||
|
soc.webroot = 'no match startswith'
|
||||||
|
self.stubs.Set(SimpleHTTPRequestHandler,
|
||||||
|
'send_response',
|
||||||
|
lambda *args: None)
|
||||||
|
soc.do_GET()
|
||||||
|
self.assertEqual(403, soc.last_code)
|
||||||
|
|
||||||
|
def testsocket_set_keepalive_options(self):
|
||||||
|
keepcnt = 12
|
||||||
|
keepidle = 34
|
||||||
|
keepintvl = 56
|
||||||
|
|
||||||
|
sock = self.server.socket('localhost',
|
||||||
|
tcp_keepcnt=keepcnt,
|
||||||
|
tcp_keepidle=keepidle,
|
||||||
|
tcp_keepintvl=keepintvl)
|
||||||
|
|
||||||
|
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
|
socket.TCP_KEEPCNT), keepcnt)
|
||||||
|
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
|
socket.TCP_KEEPIDLE), keepidle)
|
||||||
|
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
|
socket.TCP_KEEPINTVL), keepintvl)
|
||||||
|
|
||||||
|
sock = self.server.socket('localhost',
|
||||||
|
tcp_keepalive=False,
|
||||||
|
tcp_keepcnt=keepcnt,
|
||||||
|
tcp_keepidle=keepidle,
|
||||||
|
tcp_keepintvl=keepintvl)
|
||||||
|
|
||||||
|
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
|
socket.TCP_KEEPCNT), keepcnt)
|
||||||
|
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
|
socket.TCP_KEEPIDLE), keepidle)
|
||||||
|
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
|
socket.TCP_KEEPINTVL), keepintvl)
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright(c)2013 NTT corp. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
""" Unit tests for websocketproxy """
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import select
|
||||||
|
import shutil
|
||||||
|
import stubout
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from websockify import websocketproxy
|
||||||
|
|
||||||
|
|
||||||
|
class MockSocket(object):
|
||||||
|
def __init__(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def shutdown(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def close(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketProxyTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def _init_logger(self, tmpdir):
|
||||||
|
name = 'websocket-unittest'
|
||||||
|
logger = logging.getLogger(name)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
logger.propagate = True
|
||||||
|
filename = "%s.log" % (name)
|
||||||
|
handler = logging.FileHandler(filename)
|
||||||
|
handler.setFormatter(logging.Formatter("%(message)s"))
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Called automatically before each test."""
|
||||||
|
super(WebSocketProxyTest, self).setUp()
|
||||||
|
self.soc = ''
|
||||||
|
self.stubs = stubout.StubOutForTesting()
|
||||||
|
# Temporary dir for test data
|
||||||
|
self.tmpdir = tempfile.mkdtemp()
|
||||||
|
# Put log somewhere persistent
|
||||||
|
self._init_logger('./')
|
||||||
|
# Mock this out cause it screws tests up
|
||||||
|
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Called automatically after each test."""
|
||||||
|
self.stubs.UnsetAll()
|
||||||
|
shutil.rmtree(self.tmpdir)
|
||||||
|
super(WebSocketProxyTest, self).tearDown()
|
||||||
|
|
||||||
|
def _get_websockproxy(self, **kwargs):
|
||||||
|
return websocketproxy.WebSocketProxy(key=self.tmpdir,
|
||||||
|
web=self.tmpdir,
|
||||||
|
record=self.tmpdir,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def test_run_wrap_cmd(self):
|
||||||
|
web_socket_proxy = self._get_websockproxy()
|
||||||
|
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
|
||||||
|
|
||||||
|
def mock_Popen(*args, **kwargs):
|
||||||
|
return '_mock_cmd'
|
||||||
|
|
||||||
|
self.stubs.Set(subprocess, 'Popen', mock_Popen)
|
||||||
|
web_socket_proxy.run_wrap_cmd()
|
||||||
|
self.assertEquals(web_socket_proxy.spawn_message, True)
|
||||||
|
|
||||||
|
def test_started(self):
|
||||||
|
web_socket_proxy = self._get_websockproxy()
|
||||||
|
web_socket_proxy.__dict__["spawn_message"] = False
|
||||||
|
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
|
||||||
|
|
||||||
|
def mock_run_wrap_cmd(*args, **kwargs):
|
||||||
|
web_socket_proxy.__dict__["spawn_message"] = True
|
||||||
|
|
||||||
|
self.stubs.Set(web_socket_proxy, 'run_wrap_cmd', mock_run_wrap_cmd)
|
||||||
|
web_socket_proxy.started()
|
||||||
|
self.assertEquals(web_socket_proxy.__dict__["spawn_message"], True)
|
||||||
|
|
||||||
|
def test_poll(self):
|
||||||
|
web_socket_proxy = self._get_websockproxy()
|
||||||
|
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
|
||||||
|
web_socket_proxy.__dict__["wrap_mode"] = "respawn"
|
||||||
|
web_socket_proxy.__dict__["wrap_times"] = [99999999]
|
||||||
|
web_socket_proxy.__dict__["spawn_message"] = True
|
||||||
|
web_socket_proxy.__dict__["cmd"] = None
|
||||||
|
self.stubs.Set(time, 'time', lambda: 100000000.000)
|
||||||
|
web_socket_proxy.poll()
|
||||||
|
self.assertEquals(web_socket_proxy.spawn_message, False)
|
||||||
|
|
||||||
|
def test_new_client(self):
|
||||||
|
web_socket_proxy = self._get_websockproxy()
|
||||||
|
web_socket_proxy.__dict__["verbose"] = "verbose"
|
||||||
|
web_socket_proxy.__dict__["daemon"] = None
|
||||||
|
web_socket_proxy.__dict__["client"] = "client"
|
||||||
|
|
||||||
|
self.stubs.Set(web_socket_proxy, 'socket', MockSocket)
|
||||||
|
|
||||||
|
def mock_select(*args, **kwargs):
|
||||||
|
ins = None
|
||||||
|
outs = None
|
||||||
|
excepts = "excepts"
|
||||||
|
return ins, outs, excepts
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', mock_select)
|
||||||
|
self.assertRaises(Exception, web_socket_proxy.new_websocket_client)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Tox (http://tox.testrun.org/) is a tool for running tests
|
||||||
|
# in multiple virtualenvs. This configuration file will run the
|
||||||
|
# test suite on all supported python versions. To use it, "pip install tox"
|
||||||
|
# and then run "tox" from this directory.
|
||||||
|
|
||||||
|
[tox]
|
||||||
|
envlist = py24,py25,py26,py27,py30
|
||||||
|
setupdir = ../
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
commands = nosetests {posargs}
|
||||||
|
deps =
|
||||||
|
mox
|
||||||
|
nose
|
||||||
|
|
||||||
|
# At some point we should enable this since tox epdctes it to exist but
|
||||||
|
# the code will need pep8ising first.
|
||||||
|
#[testenv:pep8]
|
||||||
|
#commands = flake8
|
||||||
|
#dep = flake8
|
||||||
+4
-7
@@ -5,18 +5,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.join(os.path.dirname(__file__), ".."))
|
||||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
from websockify.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))
|
|
||||||
|
|
||||||
|
|||||||
-946
@@ -1,946 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
'''
|
|
||||||
Python WebSocket library with support for "wss://" encryption.
|
|
||||||
Copyright 2011 Joel Martin
|
|
||||||
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
|
||||||
|
|
||||||
Supports following protocol versions:
|
|
||||||
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
|
|
||||||
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
|
||||||
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
|
||||||
|
|
||||||
You can make a cert/key with openssl using:
|
|
||||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
|
||||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
import os, sys, time, errno, signal, socket, traceback, select
|
|
||||||
import array, struct
|
|
||||||
from base64 import b64encode, b64decode
|
|
||||||
|
|
||||||
# Imports that vary by python version
|
|
||||||
|
|
||||||
# python 3.0 differences
|
|
||||||
if sys.hexversion > 0x3000000:
|
|
||||||
b2s = lambda buf: buf.decode('latin_1')
|
|
||||||
s2b = lambda s: s.encode('latin_1')
|
|
||||||
s2a = lambda s: s
|
|
||||||
else:
|
|
||||||
b2s = lambda buf: buf # No-op
|
|
||||||
s2b = lambda s: s # No-op
|
|
||||||
s2a = lambda s: [ord(c) for c in s]
|
|
||||||
try: from io import StringIO
|
|
||||||
except: from cStringIO import StringIO
|
|
||||||
try: from http.server import SimpleHTTPRequestHandler
|
|
||||||
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
|
|
||||||
|
|
||||||
# python 2.6 differences
|
|
||||||
try: from hashlib import md5, sha1
|
|
||||||
except: from md5 import md5; from sha import sha as sha1
|
|
||||||
|
|
||||||
# python 2.5 differences
|
|
||||||
try:
|
|
||||||
from struct import pack, unpack_from
|
|
||||||
except:
|
|
||||||
from struct import pack
|
|
||||||
def unpack_from(fmt, buf, offset=0):
|
|
||||||
slice = buffer(buf, offset, struct.calcsize(fmt))
|
|
||||||
return struct.unpack(fmt, slice)
|
|
||||||
|
|
||||||
# Degraded functionality if these imports are missing
|
|
||||||
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
|
|
||||||
('multiprocessing', 'Multi-Processing'),
|
|
||||||
('resource', 'daemonizing')]:
|
|
||||||
try:
|
|
||||||
globals()[mod] = __import__(mod)
|
|
||||||
except ImportError:
|
|
||||||
globals()[mod] = None
|
|
||||||
print("WARNING: no '%s' module, %s is slower or disabled" % (
|
|
||||||
mod, sup))
|
|
||||||
if multiprocessing and sys.platform == 'win32':
|
|
||||||
# make sockets pickle-able/inheritable
|
|
||||||
import multiprocessing.reduction
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketServer(object):
|
|
||||||
"""
|
|
||||||
WebSockets server class.
|
|
||||||
Must be sub-classed with new_client method definition.
|
|
||||||
"""
|
|
||||||
|
|
||||||
buffer_size = 65536
|
|
||||||
|
|
||||||
|
|
||||||
server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
|
||||||
Upgrade: WebSocket\r
|
|
||||||
Connection: Upgrade\r
|
|
||||||
%sWebSocket-Origin: %s\r
|
|
||||||
%sWebSocket-Location: %s://%s%s\r
|
|
||||||
"""
|
|
||||||
|
|
||||||
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
|
|
||||||
Upgrade: websocket\r
|
|
||||||
Connection: Upgrade\r
|
|
||||||
Sec-WebSocket-Accept: %s\r
|
|
||||||
"""
|
|
||||||
|
|
||||||
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
||||||
|
|
||||||
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
|
|
||||||
|
|
||||||
# An exception before the WebSocket connection was established
|
|
||||||
class EClose(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# An exception while the WebSocket client was connected
|
|
||||||
class CClose(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
|
|
||||||
verbose=False, cert='', key='', ssl_only=None,
|
|
||||||
daemon=False, record='', web='',
|
|
||||||
run_once=False, timeout=0):
|
|
||||||
|
|
||||||
# settings
|
|
||||||
self.verbose = verbose
|
|
||||||
self.listen_host = listen_host
|
|
||||||
self.listen_port = listen_port
|
|
||||||
self.prefer_ipv6 = source_is_ipv6
|
|
||||||
self.ssl_only = ssl_only
|
|
||||||
self.daemon = daemon
|
|
||||||
self.run_once = run_once
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
self.launch_time = time.time()
|
|
||||||
self.ws_connection = False
|
|
||||||
self.handler_id = 1
|
|
||||||
|
|
||||||
# Make paths settings absolute
|
|
||||||
self.cert = os.path.abspath(cert)
|
|
||||||
self.key = self.web = self.record = ''
|
|
||||||
if key:
|
|
||||||
self.key = os.path.abspath(key)
|
|
||||||
if web:
|
|
||||||
self.web = os.path.abspath(web)
|
|
||||||
if record:
|
|
||||||
self.record = os.path.abspath(record)
|
|
||||||
|
|
||||||
if self.web:
|
|
||||||
os.chdir(self.web)
|
|
||||||
|
|
||||||
# Sanity checks
|
|
||||||
if not ssl and self.ssl_only:
|
|
||||||
raise Exception("No 'ssl' module and SSL-only specified")
|
|
||||||
if self.daemon and not resource:
|
|
||||||
raise Exception("Module 'resource' required to daemonize")
|
|
||||||
|
|
||||||
# Show configuration
|
|
||||||
print("WebSocket server settings:")
|
|
||||||
print(" - Listen on %s:%s" % (
|
|
||||||
self.listen_host, self.listen_port))
|
|
||||||
print(" - Flash security policy server")
|
|
||||||
if self.web:
|
|
||||||
print(" - Web server. Web root: %s" % self.web)
|
|
||||||
if ssl:
|
|
||||||
if os.path.exists(self.cert):
|
|
||||||
print(" - SSL/TLS support")
|
|
||||||
if self.ssl_only:
|
|
||||||
print(" - Deny non-SSL/TLS connections")
|
|
||||||
else:
|
|
||||||
print(" - No SSL/TLS support (no cert file)")
|
|
||||||
else:
|
|
||||||
print(" - No SSL/TLS support (no 'ssl' module)")
|
|
||||||
if self.daemon:
|
|
||||||
print(" - Backgrounding (daemon)")
|
|
||||||
if self.record:
|
|
||||||
print(" - Recording to '%s.*'" % self.record)
|
|
||||||
|
|
||||||
#
|
|
||||||
# WebSocketServer static methods
|
|
||||||
#
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def socket(host, port=None, connect=False, prefer_ipv6=False, unix_socket=None, use_ssl=False):
|
|
||||||
""" Resolve a host (and optional port) to an IPv4 or IPv6
|
|
||||||
address. Create a socket. Bind to it if listen is set,
|
|
||||||
otherwise connect to it. Return the socket.
|
|
||||||
"""
|
|
||||||
flags = 0
|
|
||||||
if host == '':
|
|
||||||
host = None
|
|
||||||
if connect and not (port or unix_socket):
|
|
||||||
raise Exception("Connect mode requires a port")
|
|
||||||
if use_ssl and not ssl:
|
|
||||||
raise Exception("SSL socket requested but Python SSL module not loaded.");
|
|
||||||
if not connect and use_ssl:
|
|
||||||
raise Exception("SSL only supported in connect mode (for now)")
|
|
||||||
if not connect:
|
|
||||||
flags = flags | socket.AI_PASSIVE
|
|
||||||
|
|
||||||
if not unix_socket:
|
|
||||||
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
|
|
||||||
socket.IPPROTO_TCP, flags)
|
|
||||||
if not addrs:
|
|
||||||
raise Exception("Could not resolve host '%s'" % host)
|
|
||||||
addrs.sort(key=lambda x: x[0])
|
|
||||||
if prefer_ipv6:
|
|
||||||
addrs.reverse()
|
|
||||||
sock = socket.socket(addrs[0][0], addrs[0][1])
|
|
||||||
if connect:
|
|
||||||
sock.connect(addrs[0][4])
|
|
||||||
if use_ssl:
|
|
||||||
sock = ssl.wrap_socket(sock)
|
|
||||||
else:
|
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
sock.bind(addrs[0][4])
|
|
||||||
sock.listen(100)
|
|
||||||
else:
|
|
||||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
||||||
sock.connect(unix_socket)
|
|
||||||
|
|
||||||
return sock
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def daemonize(keepfd=None, chdir='/'):
|
|
||||||
os.umask(0)
|
|
||||||
if chdir:
|
|
||||||
os.chdir(chdir)
|
|
||||||
else:
|
|
||||||
os.chdir('/')
|
|
||||||
os.setgid(os.getgid()) # relinquish elevations
|
|
||||||
os.setuid(os.getuid()) # relinquish elevations
|
|
||||||
|
|
||||||
# Double fork to daemonize
|
|
||||||
if os.fork() > 0: os._exit(0) # Parent exits
|
|
||||||
os.setsid() # Obtain new process group
|
|
||||||
if os.fork() > 0: os._exit(0) # Parent exits
|
|
||||||
|
|
||||||
# Signal handling
|
|
||||||
def terminate(a,b): os._exit(0)
|
|
||||||
signal.signal(signal.SIGTERM, terminate)
|
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
||||||
|
|
||||||
# Close open files
|
|
||||||
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
|
||||||
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
|
||||||
for fd in reversed(range(maxfd)):
|
|
||||||
try:
|
|
||||||
if fd != keepfd:
|
|
||||||
os.close(fd)
|
|
||||||
except OSError:
|
|
||||||
_, exc, _ = sys.exc_info()
|
|
||||||
if exc.errno != errno.EBADF: raise
|
|
||||||
|
|
||||||
# Redirect I/O to /dev/null
|
|
||||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
|
|
||||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
|
|
||||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def unmask(buf, f):
|
|
||||||
pstart = f['hlen'] + 4
|
|
||||||
pend = pstart + f['length']
|
|
||||||
if numpy:
|
|
||||||
b = c = s2b('')
|
|
||||||
if f['length'] >= 4:
|
|
||||||
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
|
||||||
offset=f['hlen'], count=1)
|
|
||||||
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
|
||||||
offset=pstart, count=int(f['length'] / 4))
|
|
||||||
#b = numpy.bitwise_xor(data, mask).data
|
|
||||||
b = numpy.bitwise_xor(data, mask).tostring()
|
|
||||||
|
|
||||||
if f['length'] % 4:
|
|
||||||
#print("Partial unmask")
|
|
||||||
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
|
||||||
offset=f['hlen'], count=(f['length'] % 4))
|
|
||||||
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
|
||||||
offset=pend - (f['length'] % 4),
|
|
||||||
count=(f['length'] % 4))
|
|
||||||
c = numpy.bitwise_xor(data, mask).tostring()
|
|
||||||
return b + c
|
|
||||||
else:
|
|
||||||
# Slower fallback
|
|
||||||
data = array.array('B')
|
|
||||||
mask = s2a(f['mask'])
|
|
||||||
data.fromstring(buf[pstart:pend])
|
|
||||||
for i in range(len(data)):
|
|
||||||
data[i] ^= mask[i % 4]
|
|
||||||
return data.tostring()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def encode_hybi(buf, opcode, base64=False):
|
|
||||||
""" Encode a HyBi style WebSocket frame.
|
|
||||||
Optional opcode:
|
|
||||||
0x0 - continuation
|
|
||||||
0x1 - text frame (base64 encode buf)
|
|
||||||
0x2 - binary frame (use raw buf)
|
|
||||||
0x8 - connection close
|
|
||||||
0x9 - ping
|
|
||||||
0xA - pong
|
|
||||||
"""
|
|
||||||
if base64:
|
|
||||||
buf = b64encode(buf)
|
|
||||||
|
|
||||||
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
|
|
||||||
payload_len = len(buf)
|
|
||||||
if payload_len <= 125:
|
|
||||||
header = pack('>BB', b1, payload_len)
|
|
||||||
elif payload_len > 125 and payload_len < 65536:
|
|
||||||
header = pack('>BBH', b1, 126, payload_len)
|
|
||||||
elif payload_len >= 65536:
|
|
||||||
header = pack('>BBQ', b1, 127, payload_len)
|
|
||||||
|
|
||||||
#print("Encoded: %s" % repr(header + buf))
|
|
||||||
|
|
||||||
return header + buf, len(header), 0
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def decode_hybi(buf, base64=False):
|
|
||||||
""" Decode HyBi style WebSocket packets.
|
|
||||||
Returns:
|
|
||||||
{'fin' : 0_or_1,
|
|
||||||
'opcode' : number,
|
|
||||||
'mask' : 32_bit_number,
|
|
||||||
'hlen' : header_bytes_number,
|
|
||||||
'length' : payload_bytes_number,
|
|
||||||
'payload' : decoded_buffer,
|
|
||||||
'left' : bytes_left_number,
|
|
||||||
'close_code' : number,
|
|
||||||
'close_reason' : string}
|
|
||||||
"""
|
|
||||||
|
|
||||||
f = {'fin' : 0,
|
|
||||||
'opcode' : 0,
|
|
||||||
'mask' : 0,
|
|
||||||
'hlen' : 2,
|
|
||||||
'length' : 0,
|
|
||||||
'payload' : None,
|
|
||||||
'left' : 0,
|
|
||||||
'close_code' : 1000,
|
|
||||||
'close_reason' : ''}
|
|
||||||
|
|
||||||
blen = len(buf)
|
|
||||||
f['left'] = blen
|
|
||||||
|
|
||||||
if blen < f['hlen']:
|
|
||||||
return f # Incomplete frame header
|
|
||||||
|
|
||||||
b1, b2 = unpack_from(">BB", buf)
|
|
||||||
f['opcode'] = b1 & 0x0f
|
|
||||||
f['fin'] = (b1 & 0x80) >> 7
|
|
||||||
has_mask = (b2 & 0x80) >> 7
|
|
||||||
|
|
||||||
f['length'] = b2 & 0x7f
|
|
||||||
|
|
||||||
if f['length'] == 126:
|
|
||||||
f['hlen'] = 4
|
|
||||||
if blen < f['hlen']:
|
|
||||||
return f # Incomplete frame header
|
|
||||||
(f['length'],) = unpack_from('>xxH', buf)
|
|
||||||
elif f['length'] == 127:
|
|
||||||
f['hlen'] = 10
|
|
||||||
if blen < f['hlen']:
|
|
||||||
return f # Incomplete frame header
|
|
||||||
(f['length'],) = unpack_from('>xxQ', buf)
|
|
||||||
|
|
||||||
full_len = f['hlen'] + has_mask * 4 + f['length']
|
|
||||||
|
|
||||||
if blen < full_len: # Incomplete frame
|
|
||||||
return f # Incomplete frame header
|
|
||||||
|
|
||||||
# Number of bytes that are part of the next frame(s)
|
|
||||||
f['left'] = blen - full_len
|
|
||||||
|
|
||||||
# Process 1 frame
|
|
||||||
if has_mask:
|
|
||||||
# unmask payload
|
|
||||||
f['mask'] = buf[f['hlen']:f['hlen']+4]
|
|
||||||
f['payload'] = WebSocketServer.unmask(buf, f)
|
|
||||||
else:
|
|
||||||
print("Unmasked frame: %s" % repr(buf))
|
|
||||||
f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
|
|
||||||
|
|
||||||
if base64 and f['opcode'] in [1, 2]:
|
|
||||||
try:
|
|
||||||
f['payload'] = b64decode(f['payload'])
|
|
||||||
except:
|
|
||||||
print("Exception while b64decoding buffer: %s" %
|
|
||||||
repr(buf))
|
|
||||||
raise
|
|
||||||
|
|
||||||
if f['opcode'] == 0x08:
|
|
||||||
if f['length'] >= 2:
|
|
||||||
f['close_code'] = unpack_from(">H", f['payload'])[0]
|
|
||||||
if f['length'] > 3:
|
|
||||||
f['close_reason'] = f['payload'][2:]
|
|
||||||
|
|
||||||
return f
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def encode_hixie(buf):
|
|
||||||
return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def decode_hixie(buf):
|
|
||||||
end = buf.find(s2b('\xff'))
|
|
||||||
return {'payload': b64decode(buf[1:end]),
|
|
||||||
'hlen': 1,
|
|
||||||
'length': end - 1,
|
|
||||||
'left': len(buf) - (end + 1)}
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def gen_md5(keys):
|
|
||||||
""" Generate hash value for WebSockets hixie-76. """
|
|
||||||
key1 = keys['Sec-WebSocket-Key1']
|
|
||||||
key2 = keys['Sec-WebSocket-Key2']
|
|
||||||
key3 = keys['key3']
|
|
||||||
spaces1 = key1.count(" ")
|
|
||||||
spaces2 = key2.count(" ")
|
|
||||||
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
|
|
||||||
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
|
|
||||||
|
|
||||||
return b2s(md5(pack('>II8s',
|
|
||||||
int(num1), int(num2), key3)).digest())
|
|
||||||
|
|
||||||
#
|
|
||||||
# WebSocketServer logging/output functions
|
|
||||||
#
|
|
||||||
|
|
||||||
def traffic(self, token="."):
|
|
||||||
""" Show traffic flow in verbose mode. """
|
|
||||||
if self.verbose and not self.daemon:
|
|
||||||
sys.stdout.write(token)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
def msg(self, msg):
|
|
||||||
""" Output message with handler_id prefix. """
|
|
||||||
if not self.daemon:
|
|
||||||
print("% 3d: %s" % (self.handler_id, msg))
|
|
||||||
|
|
||||||
def vmsg(self, msg):
|
|
||||||
""" Same as msg() but only if verbose. """
|
|
||||||
if self.verbose:
|
|
||||||
self.msg(msg)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Main WebSocketServer methods
|
|
||||||
#
|
|
||||||
def send_frames(self, bufs=None):
|
|
||||||
""" Encode and send WebSocket frames. Any frames already
|
|
||||||
queued will be sent first. If buf is not set then only queued
|
|
||||||
frames will be sent. Returns the number of pending frames that
|
|
||||||
could not be fully sent. If returned pending frames is greater
|
|
||||||
than 0, then the caller should call again when the socket is
|
|
||||||
ready. """
|
|
||||||
|
|
||||||
tdelta = int(time.time()*1000) - self.start_time
|
|
||||||
|
|
||||||
if bufs:
|
|
||||||
for buf in bufs:
|
|
||||||
if self.version.startswith("hybi"):
|
|
||||||
if self.base64:
|
|
||||||
encbuf, lenhead, lentail = self.encode_hybi(
|
|
||||||
buf, opcode=1, base64=True)
|
|
||||||
else:
|
|
||||||
encbuf, lenhead, lentail = self.encode_hybi(
|
|
||||||
buf, opcode=2, base64=False)
|
|
||||||
|
|
||||||
else:
|
|
||||||
encbuf, lenhead, lentail = self.encode_hixie(buf)
|
|
||||||
|
|
||||||
if self.rec:
|
|
||||||
self.rec.write("%s,\n" %
|
|
||||||
repr("{%s{" % tdelta
|
|
||||||
+ encbuf[lenhead:-lentail]))
|
|
||||||
|
|
||||||
self.send_parts.append(encbuf)
|
|
||||||
|
|
||||||
while self.send_parts:
|
|
||||||
# Send pending frames
|
|
||||||
buf = self.send_parts.pop(0)
|
|
||||||
sent = self.client.send(buf)
|
|
||||||
|
|
||||||
if sent == len(buf):
|
|
||||||
self.traffic("<")
|
|
||||||
else:
|
|
||||||
self.traffic("<.")
|
|
||||||
self.send_parts.insert(0, buf[sent:])
|
|
||||||
break
|
|
||||||
|
|
||||||
return len(self.send_parts)
|
|
||||||
|
|
||||||
def recv_frames(self):
|
|
||||||
""" Receive and decode WebSocket frames.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(bufs_list, closed_string)
|
|
||||||
"""
|
|
||||||
|
|
||||||
closed = False
|
|
||||||
bufs = []
|
|
||||||
tdelta = int(time.time()*1000) - self.start_time
|
|
||||||
|
|
||||||
buf = self.client.recv(self.buffer_size)
|
|
||||||
if len(buf) == 0:
|
|
||||||
closed = {'code': 1000, 'reason': "Client closed abruptly"}
|
|
||||||
return bufs, closed
|
|
||||||
|
|
||||||
if self.recv_part:
|
|
||||||
# Add partially received frames to current read buffer
|
|
||||||
buf = self.recv_part + buf
|
|
||||||
self.recv_part = None
|
|
||||||
|
|
||||||
while buf:
|
|
||||||
if self.version.startswith("hybi"):
|
|
||||||
|
|
||||||
frame = self.decode_hybi(buf, base64=self.base64)
|
|
||||||
#print("Received buf: %s, frame: %s" % (repr(buf), frame))
|
|
||||||
|
|
||||||
if frame['payload'] == None:
|
|
||||||
# Incomplete/partial frame
|
|
||||||
self.traffic("}.")
|
|
||||||
if frame['left'] > 0:
|
|
||||||
self.recv_part = buf[-frame['left']:]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if frame['opcode'] == 0x8: # connection close
|
|
||||||
closed = {'code': frame['close_code'],
|
|
||||||
'reason': frame['close_reason']}
|
|
||||||
break
|
|
||||||
|
|
||||||
else:
|
|
||||||
if buf[0:2] == s2b('\xff\x00'):
|
|
||||||
closed = {'code': 1000,
|
|
||||||
'reason': "Client sent orderly close frame"}
|
|
||||||
break
|
|
||||||
|
|
||||||
elif buf[0:2] == s2b('\x00\xff'):
|
|
||||||
buf = buf[2:]
|
|
||||||
continue # No-op
|
|
||||||
|
|
||||||
elif buf.count(s2b('\xff')) == 0:
|
|
||||||
# Partial frame
|
|
||||||
self.traffic("}.")
|
|
||||||
self.recv_part = buf
|
|
||||||
break
|
|
||||||
|
|
||||||
frame = self.decode_hixie(buf)
|
|
||||||
|
|
||||||
self.traffic("}")
|
|
||||||
|
|
||||||
if self.rec:
|
|
||||||
start = frame['hlen']
|
|
||||||
end = frame['hlen'] + frame['length']
|
|
||||||
self.rec.write("%s,\n" %
|
|
||||||
repr("}%s}" % tdelta + buf[start:end]))
|
|
||||||
|
|
||||||
|
|
||||||
bufs.append(frame['payload'])
|
|
||||||
|
|
||||||
if frame['left']:
|
|
||||||
buf = buf[-frame['left']:]
|
|
||||||
else:
|
|
||||||
buf = ''
|
|
||||||
|
|
||||||
return bufs, closed
|
|
||||||
|
|
||||||
def send_close(self, code=1000, reason=''):
|
|
||||||
""" Send a WebSocket orderly close frame. """
|
|
||||||
|
|
||||||
if self.version.startswith("hybi"):
|
|
||||||
msg = pack(">H%ds" % len(reason), code, reason)
|
|
||||||
|
|
||||||
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
|
||||||
self.client.send(buf)
|
|
||||||
|
|
||||||
elif self.version == "hixie-76":
|
|
||||||
buf = s2b('\xff\x00')
|
|
||||||
self.client.send(buf)
|
|
||||||
|
|
||||||
# No orderly close for 75
|
|
||||||
|
|
||||||
def do_websocket_handshake(self, headers, path):
|
|
||||||
h = self.headers = headers
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
prot = 'WebSocket-Protocol'
|
|
||||||
protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
|
|
||||||
|
|
||||||
ver = h.get('Sec-WebSocket-Version')
|
|
||||||
if ver:
|
|
||||||
# HyBi/IETF version of the protocol
|
|
||||||
|
|
||||||
# HyBi-07 report version 7
|
|
||||||
# HyBi-08 - HyBi-12 report version 8
|
|
||||||
# HyBi-13 reports version 13
|
|
||||||
if ver in ['7', '8', '13']:
|
|
||||||
self.version = "hybi-%02d" % int(ver)
|
|
||||||
else:
|
|
||||||
raise self.EClose('Unsupported protocol version %s' % ver)
|
|
||||||
|
|
||||||
key = h['Sec-WebSocket-Key']
|
|
||||||
|
|
||||||
# Choose binary if client supports it
|
|
||||||
if 'binary' in protocols:
|
|
||||||
self.base64 = False
|
|
||||||
elif 'base64' in protocols:
|
|
||||||
self.base64 = True
|
|
||||||
else:
|
|
||||||
raise self.EClose("Client must support 'binary' or 'base64' protocol")
|
|
||||||
|
|
||||||
# Generate the hash value for the accept header
|
|
||||||
accept = b64encode(sha1(s2b(key + self.GUID)).digest())
|
|
||||||
|
|
||||||
response = self.server_handshake_hybi % b2s(accept)
|
|
||||||
if self.base64:
|
|
||||||
response += "Sec-WebSocket-Protocol: base64\r\n"
|
|
||||||
else:
|
|
||||||
response += "Sec-WebSocket-Protocol: binary\r\n"
|
|
||||||
response += "\r\n"
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Hixie version of the protocol (75 or 76)
|
|
||||||
|
|
||||||
if h.get('key3'):
|
|
||||||
trailer = self.gen_md5(h)
|
|
||||||
pre = "Sec-"
|
|
||||||
self.version = "hixie-76"
|
|
||||||
else:
|
|
||||||
trailer = ""
|
|
||||||
pre = ""
|
|
||||||
self.version = "hixie-75"
|
|
||||||
|
|
||||||
# We only support base64 in Hixie era
|
|
||||||
self.base64 = True
|
|
||||||
|
|
||||||
response = self.server_handshake_hixie % (pre,
|
|
||||||
h['Origin'], pre, 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
|
|
||||||
|
|
||||||
|
|
||||||
def do_handshake(self, sock, address):
|
|
||||||
"""
|
|
||||||
do_handshake does the following:
|
|
||||||
- Peek at the first few bytes from the socket.
|
|
||||||
- If the connection is Flash policy request then answer it,
|
|
||||||
close the socket and return.
|
|
||||||
- If the connection is an HTTPS/SSL/TLS connection then SSL
|
|
||||||
wrap the socket.
|
|
||||||
- Read from the (possibly wrapped) socket.
|
|
||||||
- If we have received a HTTP GET request and the webserver
|
|
||||||
functionality is enabled, answer it, close the socket and
|
|
||||||
return.
|
|
||||||
- Assume we have a WebSockets connection, parse the client
|
|
||||||
handshake data.
|
|
||||||
- Send a WebSockets handshake server response.
|
|
||||||
- Return the socket for this WebSocket client.
|
|
||||||
"""
|
|
||||||
stype = ""
|
|
||||||
ready = select.select([sock], [], [], 3)[0]
|
|
||||||
|
|
||||||
|
|
||||||
if not ready:
|
|
||||||
raise self.EClose("ignoring socket not ready")
|
|
||||||
# Peek, but do not read the data so that we have a opportunity
|
|
||||||
# to SSL wrap the socket first
|
|
||||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
|
||||||
#self.msg("Handshake [%s]" % handshake)
|
|
||||||
|
|
||||||
if handshake == "":
|
|
||||||
raise self.EClose("ignoring empty handshake")
|
|
||||||
|
|
||||||
elif handshake.startswith(s2b("<policy-file-request/>")):
|
|
||||||
# Answer Flash policy request
|
|
||||||
handshake = sock.recv(1024)
|
|
||||||
sock.send(s2b(self.policy_response))
|
|
||||||
raise self.EClose("Sending flash policy response")
|
|
||||||
|
|
||||||
elif handshake[0] in ("\x16", "\x80", 22, 128):
|
|
||||||
# SSL wrap the connection
|
|
||||||
if not ssl:
|
|
||||||
raise self.EClose("SSL connection but no 'ssl' module")
|
|
||||||
if not os.path.exists(self.cert):
|
|
||||||
raise self.EClose("SSL connection but '%s' not found"
|
|
||||||
% self.cert)
|
|
||||||
retsock = None
|
|
||||||
try:
|
|
||||||
retsock = ssl.wrap_socket(
|
|
||||||
sock,
|
|
||||||
server_side=True,
|
|
||||||
certfile=self.cert,
|
|
||||||
keyfile=self.key)
|
|
||||||
except ssl.SSLError:
|
|
||||||
_, x, _ = sys.exc_info()
|
|
||||||
if x.args[0] == ssl.SSL_ERROR_EOF:
|
|
||||||
if len(x.args) > 1:
|
|
||||||
raise self.EClose(x.args[1])
|
|
||||||
else:
|
|
||||||
raise self.EClose("Got SSL_ERROR_EOF")
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.scheme = "wss"
|
|
||||||
stype = "SSL/TLS (wss://)"
|
|
||||||
|
|
||||||
elif self.ssl_only:
|
|
||||||
raise self.EClose("non-SSL connection received but disallowed")
|
|
||||||
|
|
||||||
else:
|
|
||||||
retsock = sock
|
|
||||||
self.scheme = "ws"
|
|
||||||
stype = "Plain non-SSL (ws://)"
|
|
||||||
|
|
||||||
wsh = WSRequestHandler(retsock, address, not self.web)
|
|
||||||
if wsh.last_code == 101:
|
|
||||||
# Continue on to handle WebSocket upgrade
|
|
||||||
pass
|
|
||||||
elif wsh.last_code == 405:
|
|
||||||
raise self.EClose("Normal web request received but disallowed")
|
|
||||||
elif wsh.last_code < 200 or wsh.last_code >= 300:
|
|
||||||
raise self.EClose(wsh.last_message)
|
|
||||||
elif self.verbose:
|
|
||||||
raise self.EClose(wsh.last_message)
|
|
||||||
else:
|
|
||||||
raise self.EClose("")
|
|
||||||
|
|
||||||
response = self.do_websocket_handshake(wsh.headers, wsh.path)
|
|
||||||
|
|
||||||
self.msg("%s: %s WebSocket connection" % (address[0], stype))
|
|
||||||
self.msg("%s: Version %s, base64: '%s'" % (address[0],
|
|
||||||
self.version, self.base64))
|
|
||||||
if self.path != '/':
|
|
||||||
self.msg("%s: Path: '%s'" % (address[0], self.path))
|
|
||||||
|
|
||||||
|
|
||||||
# Send server WebSockets handshake response
|
|
||||||
#self.msg("sending response [%s]" % response)
|
|
||||||
retsock.send(s2b(response))
|
|
||||||
|
|
||||||
# Return the WebSockets socket which may be SSL wrapped
|
|
||||||
return retsock
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Events that can/should be overridden in sub-classes
|
|
||||||
#
|
|
||||||
def started(self):
|
|
||||||
""" Called after WebSockets startup """
|
|
||||||
self.vmsg("WebSockets server started")
|
|
||||||
|
|
||||||
def poll(self):
|
|
||||||
""" Run periodically while waiting for connections. """
|
|
||||||
#self.vmsg("Running poll()")
|
|
||||||
pass
|
|
||||||
|
|
||||||
def fallback_SIGCHLD(self, sig, stack):
|
|
||||||
# Reap zombies when using os.fork() (python 2.4)
|
|
||||||
self.vmsg("Got SIGCHLD, reaping zombies")
|
|
||||||
try:
|
|
||||||
result = os.waitpid(-1, os.WNOHANG)
|
|
||||||
while result[0]:
|
|
||||||
self.vmsg("Reaped child process %s" % result[0])
|
|
||||||
result = os.waitpid(-1, os.WNOHANG)
|
|
||||||
except (OSError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def do_SIGINT(self, sig, stack):
|
|
||||||
self.msg("Got SIGINT, exiting")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def top_new_client(self, startsock, address):
|
|
||||||
""" Do something with a WebSockets client connection. """
|
|
||||||
# Initialize per client settings
|
|
||||||
self.send_parts = []
|
|
||||||
self.recv_part = None
|
|
||||||
self.base64 = False
|
|
||||||
self.rec = None
|
|
||||||
self.start_time = int(time.time()*1000)
|
|
||||||
|
|
||||||
# handler process
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
self.client = self.do_handshake(startsock, address)
|
|
||||||
|
|
||||||
if self.record:
|
|
||||||
# Record raw frame data as JavaScript array
|
|
||||||
fname = "%s.%s" % (self.record,
|
|
||||||
self.handler_id)
|
|
||||||
self.msg("opening record file: %s" % fname)
|
|
||||||
self.rec = open(fname, 'w+')
|
|
||||||
self.rec.write("var VNC_frame_data = [\n")
|
|
||||||
|
|
||||||
self.ws_connection = True
|
|
||||||
self.new_client()
|
|
||||||
except self.CClose:
|
|
||||||
# Close the client
|
|
||||||
_, exc, _ = sys.exc_info()
|
|
||||||
if self.client:
|
|
||||||
self.send_close(exc.args[0], exc.args[1])
|
|
||||||
except self.EClose:
|
|
||||||
_, exc, _ = sys.exc_info()
|
|
||||||
# Connection was not a WebSockets connection
|
|
||||||
if exc.args[0]:
|
|
||||||
self.msg("%s: %s" % (address[0], exc.args[0]))
|
|
||||||
except Exception:
|
|
||||||
_, exc, _ = sys.exc_info()
|
|
||||||
self.msg("handler exception: %s" % str(exc))
|
|
||||||
if self.verbose:
|
|
||||||
self.msg(traceback.format_exc())
|
|
||||||
finally:
|
|
||||||
if self.rec:
|
|
||||||
self.rec.write("'EOF']\n")
|
|
||||||
self.rec.close()
|
|
||||||
|
|
||||||
if self.client and self.client != startsock:
|
|
||||||
# Close the SSL wrapped socket
|
|
||||||
# Original socket closed by caller
|
|
||||||
self.client.close()
|
|
||||||
|
|
||||||
def new_client(self):
|
|
||||||
""" Do something with a WebSockets client connection. """
|
|
||||||
raise("WebSocketServer.new_client() must be overloaded")
|
|
||||||
|
|
||||||
def start_server(self):
|
|
||||||
"""
|
|
||||||
Daemonize if requested. Listen for for connections. Run
|
|
||||||
do_handshake() method for each connection. If the connection
|
|
||||||
is a WebSockets client then call new_client() method (which must
|
|
||||||
be overridden) for each new client connection.
|
|
||||||
"""
|
|
||||||
lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6)
|
|
||||||
|
|
||||||
if self.daemon:
|
|
||||||
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
|
|
||||||
|
|
||||||
self.started() # Some things need to happen after daemonizing
|
|
||||||
|
|
||||||
# Allow override of SIGINT
|
|
||||||
signal.signal(signal.SIGINT, self.do_SIGINT)
|
|
||||||
if not multiprocessing:
|
|
||||||
# os.fork() (python 2.4) child reaper
|
|
||||||
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
self.client = None
|
|
||||||
startsock = None
|
|
||||||
pid = err = 0
|
|
||||||
|
|
||||||
time_elapsed = time.time() - self.launch_time
|
|
||||||
if self.timeout and time_elapsed > self.timeout:
|
|
||||||
self.msg('listener exit due to --timeout %s'
|
|
||||||
% self.timeout)
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.poll()
|
|
||||||
|
|
||||||
ready = select.select([lsock], [], [], 1)[0]
|
|
||||||
if lsock in ready:
|
|
||||||
startsock, address = lsock.accept()
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
except Exception:
|
|
||||||
_, exc, _ = sys.exc_info()
|
|
||||||
if hasattr(exc, 'errno'):
|
|
||||||
err = exc.errno
|
|
||||||
elif hasattr(exc, 'args'):
|
|
||||||
err = exc.args[0]
|
|
||||||
else:
|
|
||||||
err = exc[0]
|
|
||||||
if err == errno.EINTR:
|
|
||||||
self.vmsg("Ignoring interrupted syscall")
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
if self.run_once:
|
|
||||||
# Run in same process if run_once
|
|
||||||
self.top_new_client(startsock, address)
|
|
||||||
if self.ws_connection :
|
|
||||||
self.msg('%s: exiting due to --run-once'
|
|
||||||
% address[0])
|
|
||||||
break
|
|
||||||
elif multiprocessing:
|
|
||||||
self.vmsg('%s: new handler Process' % address[0])
|
|
||||||
p = multiprocessing.Process(
|
|
||||||
target=self.top_new_client,
|
|
||||||
args=(startsock, address))
|
|
||||||
p.start()
|
|
||||||
# child will not return
|
|
||||||
else:
|
|
||||||
# python 2.4
|
|
||||||
self.vmsg('%s: forking handler' % address[0])
|
|
||||||
pid = os.fork()
|
|
||||||
if pid == 0:
|
|
||||||
# child handler process
|
|
||||||
self.top_new_client(startsock, address)
|
|
||||||
break # child process exits
|
|
||||||
|
|
||||||
# parent process
|
|
||||||
self.handler_id += 1
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
_, exc, _ = sys.exc_info()
|
|
||||||
print("In KeyboardInterrupt")
|
|
||||||
pass
|
|
||||||
except SystemExit:
|
|
||||||
_, exc, _ = sys.exc_info()
|
|
||||||
print("In SystemExit")
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
_, exc, _ = sys.exc_info()
|
|
||||||
self.msg("handler exception: %s" % str(exc))
|
|
||||||
if self.verbose:
|
|
||||||
self.msg(traceback.format_exc())
|
|
||||||
|
|
||||||
finally:
|
|
||||||
if startsock:
|
|
||||||
startsock.close()
|
|
||||||
|
|
||||||
|
|
||||||
# HTTP handler with WebSocket upgrade support
|
|
||||||
class WSRequestHandler(SimpleHTTPRequestHandler):
|
|
||||||
def __init__(self, req, addr, only_upgrade=False):
|
|
||||||
self.only_upgrade = only_upgrade # only allow upgrades
|
|
||||||
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
|
|
||||||
|
|
||||||
def do_GET(self):
|
|
||||||
if (self.headers.get('upgrade') and
|
|
||||||
self.headers.get('upgrade').lower() == 'websocket'):
|
|
||||||
|
|
||||||
if (self.headers.get('sec-websocket-key1') or
|
|
||||||
self.headers.get('websocket-key1')):
|
|
||||||
# For Hixie-76 read out the key hash
|
|
||||||
self.headers.__setitem__('key3', self.rfile.read(8))
|
|
||||||
|
|
||||||
# Just indicate that an WebSocket upgrade is needed
|
|
||||||
self.last_code = 101
|
|
||||||
self.last_message = "101 Switching Protocols"
|
|
||||||
elif self.only_upgrade:
|
|
||||||
# Normal web request responses are disabled
|
|
||||||
self.last_code = 405
|
|
||||||
self.last_message = "405 Method Not Allowed"
|
|
||||||
else:
|
|
||||||
SimpleHTTPRequestHandler.do_GET(self)
|
|
||||||
|
|
||||||
def send_response(self, code, message=None):
|
|
||||||
# Save the status code
|
|
||||||
self.last_code = code
|
|
||||||
SimpleHTTPRequestHandler.send_response(self, code, message)
|
|
||||||
|
|
||||||
def log_message(self, f, *args):
|
|
||||||
# Save instead of printing
|
|
||||||
self.last_message = f % args
|
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
websockify
|
run
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from websocket import *
|
||||||
|
from websocketproxy import *
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -11,21 +11,20 @@ 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, logging
|
||||||
|
try: from socketserver import ForkingMixIn
|
||||||
|
except: from SocketServer import ForkingMixIn
|
||||||
|
try: from http.server import HTTPServer
|
||||||
|
except: from BaseHTTPServer import HTTPServer
|
||||||
from select import select
|
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 ProxyRequestHandler(websocket.WebSocketRequestHandler):
|
||||||
"""
|
|
||||||
Proxy traffic to and from a WebSockets client to a normal TCP
|
|
||||||
socket server target. All traffic to/from the client is base64
|
|
||||||
encoded/decoded to allow binary data to be sent/received to/from
|
|
||||||
the target.
|
|
||||||
"""
|
|
||||||
|
|
||||||
buffer_size = 65536
|
|
||||||
|
|
||||||
traffic_legend = """
|
traffic_legend = """
|
||||||
Traffic Legend:
|
Traffic Legend:
|
||||||
@@ -39,148 +38,33 @@ Traffic Legend:
|
|||||||
<. - Client send partial
|
<. - Client send partial
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def new_websocket_client(self):
|
||||||
# Save off proxy specific options
|
|
||||||
self.target_host = kwargs.pop('target_host')
|
|
||||||
self.target_port = kwargs.pop('target_port')
|
|
||||||
self.wrap_cmd = kwargs.pop('wrap_cmd')
|
|
||||||
self.wrap_mode = kwargs.pop('wrap_mode')
|
|
||||||
self.unix_target = kwargs.pop('unix_target')
|
|
||||||
self.ssl_target = kwargs.pop('ssl_target')
|
|
||||||
self.target_cfg = kwargs.pop('target_cfg')
|
|
||||||
# Last 3 timestamps command was run
|
|
||||||
self.wrap_times = [0, 0, 0]
|
|
||||||
|
|
||||||
if self.wrap_cmd:
|
|
||||||
rebinder_path = ['./', os.path.dirname(sys.argv[0])]
|
|
||||||
self.rebinder = None
|
|
||||||
|
|
||||||
for rdir in rebinder_path:
|
|
||||||
rpath = os.path.join(rdir, "rebind.so")
|
|
||||||
if os.path.exists(rpath):
|
|
||||||
self.rebinder = rpath
|
|
||||||
break
|
|
||||||
|
|
||||||
if not self.rebinder:
|
|
||||||
raise Exception("rebind.so not found, perhaps you need to run make")
|
|
||||||
self.rebinder = os.path.abspath(self.rebinder)
|
|
||||||
|
|
||||||
self.target_host = "127.0.0.1" # Loopback
|
|
||||||
# Find a free high port
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
sock.bind(('', 0))
|
|
||||||
self.target_port = sock.getsockname()[1]
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
os.environ.update({
|
|
||||||
"LD_PRELOAD": self.rebinder,
|
|
||||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
|
||||||
"REBIND_NEW_PORT": str(self.target_port)})
|
|
||||||
|
|
||||||
if self.target_cfg:
|
|
||||||
self.target_cfg = os.path.abspath(self.target_cfg)
|
|
||||||
|
|
||||||
websocket.WebSocketServer.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def run_wrap_cmd(self):
|
|
||||||
print("Starting '%s'" % " ".join(self.wrap_cmd))
|
|
||||||
self.wrap_times.append(time.time())
|
|
||||||
self.wrap_times.pop(0)
|
|
||||||
self.cmd = subprocess.Popen(
|
|
||||||
self.wrap_cmd, env=os.environ)
|
|
||||||
self.spawn_message = True
|
|
||||||
|
|
||||||
def started(self):
|
|
||||||
"""
|
|
||||||
Called after Websockets server startup (i.e. after daemonize)
|
|
||||||
"""
|
|
||||||
# Need to call wrapped command after daemonization so we can
|
|
||||||
# know when the wrapped command exits
|
|
||||||
if self.wrap_cmd:
|
|
||||||
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
|
|
||||||
elif self.unix_target:
|
|
||||||
dst_string = self.unix_target
|
|
||||||
else:
|
|
||||||
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
|
||||||
|
|
||||||
if self.target_cfg:
|
|
||||||
msg = " - proxying from %s:%s to targets in %s" % (
|
|
||||||
self.listen_host, self.listen_port, self.target_cfg)
|
|
||||||
else:
|
|
||||||
msg = " - proxying from %s:%s to %s" % (
|
|
||||||
self.listen_host, self.listen_port, dst_string)
|
|
||||||
|
|
||||||
if self.ssl_target:
|
|
||||||
msg += " (using SSL)"
|
|
||||||
|
|
||||||
print(msg + "\n")
|
|
||||||
|
|
||||||
if self.wrap_cmd:
|
|
||||||
self.run_wrap_cmd()
|
|
||||||
|
|
||||||
def poll(self):
|
|
||||||
# If we are wrapping a command, check it's status
|
|
||||||
|
|
||||||
if self.wrap_cmd and self.cmd:
|
|
||||||
ret = self.cmd.poll()
|
|
||||||
if ret != None:
|
|
||||||
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
|
|
||||||
self.cmd = None
|
|
||||||
|
|
||||||
if self.wrap_cmd and self.cmd == None:
|
|
||||||
# Response to wrapped command being gone
|
|
||||||
if self.wrap_mode == "ignore":
|
|
||||||
pass
|
|
||||||
elif self.wrap_mode == "exit":
|
|
||||||
sys.exit(ret)
|
|
||||||
elif self.wrap_mode == "respawn":
|
|
||||||
now = time.time()
|
|
||||||
avg = sum(self.wrap_times)/len(self.wrap_times)
|
|
||||||
if (now - avg) < 10:
|
|
||||||
# 3 times in the last 10 seconds
|
|
||||||
if self.spawn_message:
|
|
||||||
print("Command respawning too fast")
|
|
||||||
self.spawn_message = False
|
|
||||||
else:
|
|
||||||
self.run_wrap_cmd()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Routines above this point are run in the master listener
|
|
||||||
# process.
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
|
||||||
# Routines below this point are connection handler routines and
|
|
||||||
# will be run in a separate forked process for each connection.
|
|
||||||
#
|
|
||||||
|
|
||||||
def new_client(self):
|
|
||||||
"""
|
"""
|
||||||
Called after a new WebSocket connection has been established.
|
Called after a new WebSocket connection has been established.
|
||||||
"""
|
"""
|
||||||
# Checks if we receive a token, and look
|
# Checks if we receive a token, and look
|
||||||
# for a valid target for it then
|
# for a valid target for it then
|
||||||
if self.target_cfg:
|
if self.server.target_cfg:
|
||||||
(self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path)
|
(self.server.target_host, self.server.target_port) = self.get_target(self.server.target_cfg, self.path)
|
||||||
|
|
||||||
# Connect to the target
|
# Connect to the target
|
||||||
if self.wrap_cmd:
|
if self.server.wrap_cmd:
|
||||||
msg = "connecting to command: %s" % (" ".join(self.wrap_cmd), self.target_port)
|
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port)
|
||||||
elif self.unix_target:
|
elif self.server.unix_target:
|
||||||
msg = "connecting to unix socket: %s" % self.unix_target
|
msg = "connecting to unix socket: %s" % self.server.unix_target
|
||||||
else:
|
else:
|
||||||
msg = "connecting to: %s:%s" % (
|
msg = "connecting to: %s:%s" % (
|
||||||
self.target_host, self.target_port)
|
self.server.target_host, self.server.target_port)
|
||||||
|
|
||||||
if self.ssl_target:
|
if self.server.ssl_target:
|
||||||
msg += " (using SSL)"
|
msg += " (using SSL)"
|
||||||
self.msg(msg)
|
self.log_message(msg)
|
||||||
|
|
||||||
tsock = self.socket(self.target_host, self.target_port,
|
tsock = websocket.WebSocketServer.socket(self.server.target_host,
|
||||||
connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target)
|
self.server.target_port,
|
||||||
|
connect=True, use_ssl=self.server.ssl_target, unix_socket=self.server.unix_target)
|
||||||
|
|
||||||
if self.verbose and not self.daemon:
|
self.print_traffic(self.traffic_legend)
|
||||||
print(self.traffic_legend)
|
|
||||||
|
|
||||||
# Start proxying
|
# Start proxying
|
||||||
try:
|
try:
|
||||||
@@ -189,8 +73,9 @@ Traffic Legend:
|
|||||||
if tsock:
|
if tsock:
|
||||||
tsock.shutdown(socket.SHUT_RDWR)
|
tsock.shutdown(socket.SHUT_RDWR)
|
||||||
tsock.close()
|
tsock.close()
|
||||||
self.vmsg("%s:%s: Closed target" %(
|
if self.verbose:
|
||||||
self.target_host, self.target_port))
|
self.log_message("%s:%s: Closed target" %(
|
||||||
|
self.server.target_host, self.server.target_port))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get_target(self, target_cfg, path):
|
def get_target(self, target_cfg, path):
|
||||||
@@ -205,7 +90,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')
|
||||||
@@ -239,66 +124,203 @@ Traffic Legend:
|
|||||||
cqueue = []
|
cqueue = []
|
||||||
c_pend = 0
|
c_pend = 0
|
||||||
tqueue = []
|
tqueue = []
|
||||||
rlist = [self.client, target]
|
rlist = [self.request, target]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
wlist = []
|
wlist = []
|
||||||
|
|
||||||
if tqueue: wlist.append(target)
|
if tqueue: wlist.append(target)
|
||||||
if cqueue or c_pend: wlist.append(self.client)
|
if cqueue or c_pend: wlist.append(self.request)
|
||||||
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 target in outs:
|
if self.request in outs:
|
||||||
# Send queued client data to the target
|
|
||||||
dat = tqueue.pop(0)
|
|
||||||
sent = target.send(dat)
|
|
||||||
if sent == len(dat):
|
|
||||||
self.traffic(">")
|
|
||||||
else:
|
|
||||||
# requeue the remaining data
|
|
||||||
tqueue.insert(0, dat[sent:])
|
|
||||||
self.traffic(".>")
|
|
||||||
|
|
||||||
|
|
||||||
if target in ins:
|
|
||||||
# Receive target data, encode it and queue for client
|
|
||||||
buf = target.recv(self.buffer_size)
|
|
||||||
if len(buf) == 0:
|
|
||||||
self.vmsg("%s:%s: Target closed connection" %(
|
|
||||||
self.target_host, self.target_port))
|
|
||||||
raise self.CClose(1000, "Target closed")
|
|
||||||
|
|
||||||
cqueue.append(buf)
|
|
||||||
self.traffic("{")
|
|
||||||
|
|
||||||
|
|
||||||
if self.client in outs:
|
|
||||||
# Send queued target data to the client
|
# Send queued target data to the client
|
||||||
c_pend = self.send_frames(cqueue)
|
c_pend = self.send_frames(cqueue)
|
||||||
|
|
||||||
cqueue = []
|
cqueue = []
|
||||||
|
|
||||||
|
if self.request in ins:
|
||||||
if self.client in ins:
|
|
||||||
# Receive client data, decode it, and queue for target
|
# Receive client data, decode it, and queue for target
|
||||||
bufs, closed = self.recv_frames()
|
bufs, closed = self.recv_frames()
|
||||||
tqueue.extend(bufs)
|
tqueue.extend(bufs)
|
||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
# TODO: What about blocking on client socket?
|
# TODO: What about blocking on client socket?
|
||||||
self.vmsg("%s:%s: Client closed connection" %(
|
if self.verbose:
|
||||||
self.target_host, self.target_port))
|
self.log_message("%s:%s: Client closed connection" %(
|
||||||
|
self.server.target_host, self.server.target_port))
|
||||||
raise self.CClose(closed['code'], closed['reason'])
|
raise self.CClose(closed['code'], closed['reason'])
|
||||||
|
|
||||||
|
|
||||||
|
if target in outs:
|
||||||
|
# Send queued client data to the target
|
||||||
|
dat = tqueue.pop(0)
|
||||||
|
sent = target.send(dat)
|
||||||
|
if sent == len(dat):
|
||||||
|
self.print_traffic(">")
|
||||||
|
else:
|
||||||
|
# requeue the remaining data
|
||||||
|
tqueue.insert(0, dat[sent:])
|
||||||
|
self.print_traffic(".>")
|
||||||
|
|
||||||
|
|
||||||
|
if target in ins:
|
||||||
|
# Receive target data, encode it and queue for client
|
||||||
|
buf = target.recv(self.buffer_size)
|
||||||
|
if len(buf) == 0:
|
||||||
|
if self.verbose:
|
||||||
|
self.log_message("%s:%s: Target closed connection" %(
|
||||||
|
self.server.target_host, self.server.target_port))
|
||||||
|
raise self.CClose(1000, "Target closed")
|
||||||
|
|
||||||
|
cqueue.append(buf)
|
||||||
|
self.print_traffic("{")
|
||||||
|
|
||||||
|
class WebSocketProxy(websocket.WebSocketServer):
|
||||||
|
"""
|
||||||
|
Proxy traffic to and from a WebSockets client to a normal TCP
|
||||||
|
socket server target. All traffic to/from the client is base64
|
||||||
|
encoded/decoded to allow binary data to be sent/received to/from
|
||||||
|
the target.
|
||||||
|
"""
|
||||||
|
|
||||||
|
buffer_size = 65536
|
||||||
|
|
||||||
|
def __init__(self, RequestHandlerClass=ProxyRequestHandler, *args, **kwargs):
|
||||||
|
# Save off proxy specific options
|
||||||
|
self.target_host = kwargs.pop('target_host', None)
|
||||||
|
self.target_port = kwargs.pop('target_port', None)
|
||||||
|
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
|
||||||
|
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||||
|
self.unix_target = kwargs.pop('unix_target', None)
|
||||||
|
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||||
|
self.target_cfg = kwargs.pop('target_cfg', None)
|
||||||
|
# Last 3 timestamps command was run
|
||||||
|
self.wrap_times = [0, 0, 0]
|
||||||
|
|
||||||
|
if self.wrap_cmd:
|
||||||
|
wsdir = os.path.dirname(sys.argv[0])
|
||||||
|
rebinder_path = [os.path.join(wsdir, "..", "lib"),
|
||||||
|
os.path.join(wsdir, "..", "lib", "websockify"),
|
||||||
|
wsdir]
|
||||||
|
self.rebinder = None
|
||||||
|
|
||||||
|
for rdir in rebinder_path:
|
||||||
|
rpath = os.path.join(rdir, "rebind.so")
|
||||||
|
if os.path.exists(rpath):
|
||||||
|
self.rebinder = rpath
|
||||||
|
break
|
||||||
|
|
||||||
|
if not self.rebinder:
|
||||||
|
raise Exception("rebind.so not found, perhaps you need to run make")
|
||||||
|
self.rebinder = os.path.abspath(self.rebinder)
|
||||||
|
|
||||||
|
self.target_host = "127.0.0.1" # Loopback
|
||||||
|
# Find a free high port
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.bind(('', 0))
|
||||||
|
self.target_port = sock.getsockname()[1]
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
os.environ.update({
|
||||||
|
"LD_PRELOAD": self.rebinder,
|
||||||
|
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||||
|
"REBIND_NEW_PORT": str(self.target_port)})
|
||||||
|
|
||||||
|
websocket.WebSocketServer.__init__(self, RequestHandlerClass, *args, **kwargs)
|
||||||
|
|
||||||
|
def run_wrap_cmd(self):
|
||||||
|
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
|
||||||
|
self.wrap_times.append(time.time())
|
||||||
|
self.wrap_times.pop(0)
|
||||||
|
self.cmd = subprocess.Popen(
|
||||||
|
self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
|
||||||
|
self.spawn_message = True
|
||||||
|
|
||||||
|
def started(self):
|
||||||
|
"""
|
||||||
|
Called after Websockets server startup (i.e. after daemonize)
|
||||||
|
"""
|
||||||
|
# Need to call wrapped command after daemonization so we can
|
||||||
|
# know when the wrapped command exits
|
||||||
|
if self.wrap_cmd:
|
||||||
|
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
|
||||||
|
elif self.unix_target:
|
||||||
|
dst_string = self.unix_target
|
||||||
|
else:
|
||||||
|
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
||||||
|
|
||||||
|
if self.target_cfg:
|
||||||
|
msg = " - proxying from %s:%s to targets in %s" % (
|
||||||
|
self.listen_host, self.listen_port, self.target_cfg)
|
||||||
|
else:
|
||||||
|
msg = " - proxying from %s:%s to %s" % (
|
||||||
|
self.listen_host, self.listen_port, dst_string)
|
||||||
|
|
||||||
|
if self.ssl_target:
|
||||||
|
msg += " (using SSL)"
|
||||||
|
|
||||||
|
self.msg("%s", msg)
|
||||||
|
|
||||||
|
if self.wrap_cmd:
|
||||||
|
self.run_wrap_cmd()
|
||||||
|
|
||||||
|
def poll(self):
|
||||||
|
# If we are wrapping a command, check it's status
|
||||||
|
|
||||||
|
if self.wrap_cmd and self.cmd:
|
||||||
|
ret = self.cmd.poll()
|
||||||
|
if ret != None:
|
||||||
|
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
|
||||||
|
self.cmd = None
|
||||||
|
|
||||||
|
if self.wrap_cmd and self.cmd == None:
|
||||||
|
# Response to wrapped command being gone
|
||||||
|
if self.wrap_mode == "ignore":
|
||||||
|
pass
|
||||||
|
elif self.wrap_mode == "exit":
|
||||||
|
sys.exit(ret)
|
||||||
|
elif self.wrap_mode == "respawn":
|
||||||
|
now = time.time()
|
||||||
|
avg = sum(self.wrap_times)/len(self.wrap_times)
|
||||||
|
if (now - avg) < 10:
|
||||||
|
# 3 times in the last 10 seconds
|
||||||
|
if self.spawn_message:
|
||||||
|
self.warn("Command respawning too fast")
|
||||||
|
self.spawn_message = False
|
||||||
|
else:
|
||||||
|
self.run_wrap_cmd()
|
||||||
|
|
||||||
|
|
||||||
|
def _subprocess_setup():
|
||||||
|
# Python installs a SIGPIPE handler by default. This is usually not what
|
||||||
|
# non-Python successfulbprocesses expect.
|
||||||
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||||
|
|
||||||
|
|
||||||
|
def logger_init():
|
||||||
|
logger = logging.getLogger(WebSocketProxy.log_prefix)
|
||||||
|
logger.propagate = False
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
h = logging.StreamHandler()
|
||||||
|
h.setLevel(logging.DEBUG)
|
||||||
|
h.setFormatter(logging.Formatter("%(message)s"))
|
||||||
|
logger.addHandler(h)
|
||||||
|
|
||||||
|
|
||||||
def websockify_init():
|
def websockify_init():
|
||||||
|
logger_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]"
|
||||||
usage += "\n %prog [options]"
|
usage += "\n %prog [options]"
|
||||||
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
||||||
parser = optparse.OptionParser(usage=usage)
|
parser = optparse.OptionParser(usage=usage)
|
||||||
parser.add_option("--verbose", "-v", action="store_true",
|
parser.add_option("--verbose", "-v", action="store_true",
|
||||||
help="verbose messages and per frame traffic")
|
help="verbose messages")
|
||||||
|
parser.add_option("--traffic", action="store_true",
|
||||||
|
help="per frame traffic")
|
||||||
parser.add_option("--record",
|
parser.add_option("--record",
|
||||||
help="record sessions to FILE.[session_number]", metavar="FILE")
|
help="record sessions to FILE.[session_number]", metavar="FILE")
|
||||||
parser.add_option("--daemon", "-D",
|
parser.add_option("--daemon", "-D",
|
||||||
@@ -308,6 +330,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,
|
||||||
@@ -332,10 +357,15 @@ def websockify_init():
|
|||||||
help="Configuration file containing valid targets "
|
help="Configuration file containing valid targets "
|
||||||
"in the form 'token: host:port' or, alternatively, a "
|
"in the form 'token: host:port' or, alternatively, a "
|
||||||
"directory containing configuration files of this form")
|
"directory containing configuration files of this form")
|
||||||
|
parser.add_option("--libserver", action="store_true",
|
||||||
|
help="use Python library SocketServer engine")
|
||||||
(opts, args) = parser.parse_args()
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
|
||||||
|
|
||||||
# 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:]
|
||||||
@@ -372,9 +402,70 @@ def websockify_init():
|
|||||||
try: opts.target_port = int(opts.target_port)
|
try: opts.target_port = int(opts.target_port)
|
||||||
except: parser.error("Error parsing target port")
|
except: parser.error("Error parsing target port")
|
||||||
|
|
||||||
|
# Transform to absolute path as daemon may chdir
|
||||||
|
if opts.target_cfg:
|
||||||
|
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
||||||
|
|
||||||
# Create and start the WebSockets proxy
|
# Create and start the WebSockets proxy
|
||||||
server = WebSocketProxy(**opts.__dict__)
|
libserver = opts.libserver
|
||||||
server.start_server()
|
del opts.libserver
|
||||||
|
if libserver:
|
||||||
|
# Use standard Python SocketServer framework
|
||||||
|
server = LibProxyServer(**opts.__dict__)
|
||||||
|
server.serve_forever()
|
||||||
|
else:
|
||||||
|
# Use internal service framework
|
||||||
|
server = WebSocketProxy(**opts.__dict__)
|
||||||
|
server.start_server()
|
||||||
|
|
||||||
|
|
||||||
|
class LibProxyServer(ForkingMixIn, HTTPServer):
|
||||||
|
"""
|
||||||
|
Just like WebSocketProxy, but uses standard Python SocketServer
|
||||||
|
framework.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs):
|
||||||
|
# Save off proxy specific options
|
||||||
|
self.target_host = kwargs.pop('target_host', None)
|
||||||
|
self.target_port = kwargs.pop('target_port', None)
|
||||||
|
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
|
||||||
|
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||||
|
self.unix_target = kwargs.pop('unix_target', None)
|
||||||
|
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||||
|
self.target_cfg = kwargs.pop('target_cfg', None)
|
||||||
|
self.daemon = False
|
||||||
|
self.target_cfg = None
|
||||||
|
|
||||||
|
# Server configuration
|
||||||
|
listen_host = kwargs.pop('listen_host', '')
|
||||||
|
listen_port = kwargs.pop('listen_port', None)
|
||||||
|
web = kwargs.pop('web', '')
|
||||||
|
|
||||||
|
# Configuration affecting base request handler
|
||||||
|
self.only_upgrade = not web
|
||||||
|
self.verbose = kwargs.pop('verbose', False)
|
||||||
|
record = kwargs.pop('record', '')
|
||||||
|
if record:
|
||||||
|
self.record = os.path.abspath(record)
|
||||||
|
self.run_once = kwargs.pop('run_once', False)
|
||||||
|
self.handler_id = 0
|
||||||
|
|
||||||
|
for arg in kwargs.keys():
|
||||||
|
print("warning: option %s ignored when using --libserver" % arg)
|
||||||
|
|
||||||
|
if web:
|
||||||
|
os.chdir(web)
|
||||||
|
|
||||||
|
HTTPServer.__init__(self, (listen_host, listen_port),
|
||||||
|
RequestHandlerClass)
|
||||||
|
|
||||||
|
|
||||||
|
def process_request(self, request, client_address):
|
||||||
|
"""Override process_request to implement a counter"""
|
||||||
|
self.handler_id += 1
|
||||||
|
ForkingMixIn.process_request(self, request, client_address)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
websockify_init()
|
websockify_init()
|
||||||
Reference in New Issue
Block a user