Compare commits
173 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 | |||
| 17175afd73 | |||
| 53b074e8e7 | |||
| 068065e465 | |||
| 376872d993 | |||
| f55362ff07 | |||
| 36bdb09630 | |||
| d575e571fd | |||
| 00e9d3bf29 | |||
| 26e8095244 | |||
| e4b9d510f1 | |||
| 65e96176cb | |||
| 52beba8695 | |||
| e17e1158d8 | |||
| 2a8df6327e | |||
| 67d4c17516 | |||
| 1f5b492e10 | |||
| cb925eb486 | |||
| e295098330 | |||
| cddc1613ff | |||
| 233b622e47 | |||
| c7ba8c7826 | |||
| 89d2c92474 | |||
| d24f474362 | |||
| f3054df53a | |||
| c8018f29c9 | |||
| 763d2d7c1c |
@@ -4,3 +4,8 @@
|
||||
other/.lein-deps-sum
|
||||
other/classes
|
||||
other/lib
|
||||
other/node_modules
|
||||
.project
|
||||
.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
|
||||
=======
|
||||
|
||||
0.1.0
|
||||
-----------------
|
||||
0.6.1 - May 11, 2015
|
||||
--------------------
|
||||
|
||||
* **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
|
||||
|
||||
0.6.0 - Feb 18, 2014
|
||||
--------------------
|
||||
|
||||
* **NOTE** : 0.6.0 will break existing code that sub-classes WebsocketProxy
|
||||
* Refactor to use standard SocketServer RequestHandler design
|
||||
* Fix zombie process bug on certain systems when using multiprocessing
|
||||
* Add better unit tests
|
||||
* Log information via python `logging` module
|
||||
|
||||
0.5.1 - Jun 27, 2013
|
||||
--------------------
|
||||
|
||||
* use upstream einaros/ws (>=0.4.27) with websockify.js
|
||||
* file_only and no_parent security options for WSRequestHandler
|
||||
* Update build of web-socket-js (c0855c6cae)
|
||||
* add include/web-socket-js-project submodule to gimite/web-socket-js
|
||||
for DSFG compliance.
|
||||
* drop Hixie protocol support
|
||||
|
||||
0.4.1 - Mar 12, 2013
|
||||
--------------------
|
||||
|
||||
* ***NOTE*** : 0.5.0 will drop Hixie protocol support
|
||||
* add include/ directory and remove some dev files from source
|
||||
distribution.
|
||||
|
||||
0.4.0 - Mar 12, 2013
|
||||
--------------------
|
||||
|
||||
* ***NOTE*** : 0.5.0 will drop Hixie protocol support
|
||||
* use Buffer base64 support in Node.js implementation
|
||||
|
||||
0.3.0 - Jan 15, 2013
|
||||
--------------------
|
||||
|
||||
* refactor into modules: websocket, websocketproxy
|
||||
* switch to web-socket-js that uses IETF 6455
|
||||
* change to MPL 2.0 license for include/*.js
|
||||
* fix session recording
|
||||
|
||||
0.2.1 - Oct 15, 2012
|
||||
--------------------
|
||||
|
||||
* re-released with updated version number
|
||||
|
||||
0.2.0 - Sep 17, 2012
|
||||
--------------------
|
||||
|
||||
* Binary data support in websock.js
|
||||
* Target config file/dir and multiple targets with token selector
|
||||
* IPv6 fixes
|
||||
* SSL target support
|
||||
* Proxy to/from unix socket
|
||||
|
||||
|
||||
0.1.0 - May 11, 2012
|
||||
--------------------
|
||||
|
||||
* Initial versioned release.
|
||||
|
||||
|
||||
+8
-3
@@ -1,11 +1,16 @@
|
||||
websockify is licensed under the LGPL version 3 (see docs/LICENSE.GPL-3 and
|
||||
docs/LICENSE.LGPL-3) with the following exceptions:
|
||||
|
||||
include/base64.js : Choice of MIT 1.1, GPL-2 or LGPL-2.1
|
||||
|
||||
include/web-socket-js/ : New BSD license. Source code at
|
||||
include/websock.js : MPL 2.0
|
||||
|
||||
include/base64.js : MPL 2.0
|
||||
|
||||
include/des.js : Various BSD style licenses
|
||||
|
||||
include/web-socket-js/ : New BSD license (3-clause). Source code at
|
||||
https://github.com/gimite/web-socket-js
|
||||
|
||||
other/kumina.c : Simplified BSD license (2 clause).
|
||||
Original source at
|
||||
https://github.com/kumina/wsproxy
|
||||
|
||||
|
||||
+2
-1
@@ -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
|
||||
the target in both directions.
|
||||
|
||||
### News/help/contact
|
||||
|
||||
Notable commits, announcements and news are posted to
|
||||
<a href="http://www.twitter.com/noVNC">@noVNC</a>
|
||||
|
||||
If you are a websockify developer/integrator/user (or want to be)
|
||||
please join the <a
|
||||
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC/websockify
|
||||
discussion group</a>
|
||||
|
||||
Bugs and feature requests can be submitted via [github
|
||||
issues](https://github.com/kanaka/websockify/issues).
|
||||
|
||||
If you want to show appreciation for websockify you could donate to a great
|
||||
non-profits such as: [Compassion
|
||||
International](http://www.compassion.com/), [SIL](http://www.sil.org),
|
||||
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
|
||||
Foundation](https://www.eff.org/), [Against Malaria
|
||||
Foundation](http://www.againstmalaria.com/), [Nothing But
|
||||
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
|
||||
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
|
||||
|
||||
### WebSockets binary data
|
||||
|
||||
Websockify supports all versions of the WebSockets protocol (Hixie and
|
||||
HyBI). The older Hixie versions of the protocol only support UTF-8
|
||||
text payloads. In order to transport binary data over UTF-8 an
|
||||
encoding must used to encapsulate the data within UTF-8. Websockify
|
||||
uses base64 to encode all traffic to and from the client. This does
|
||||
not affect the data between websockify and the server.
|
||||
Starting with websockify 0.5.0, only the HyBi / IETF
|
||||
6455 WebSocket protocol is supported.
|
||||
|
||||
Websockify negotiates whether to base64 encode traffic to and from the
|
||||
client via the subprotocol header (Sec-WebSocket-Protocol). The valid
|
||||
subprotocol values are 'binary' and 'base64' and if the client sends
|
||||
both then the server (the python implementation) will prefer 'binary'.
|
||||
The 'binary' subprotocol indicates that the data will be sent raw
|
||||
using binary WebSocket frames. Some HyBi clients (such as the Flash
|
||||
fallback and older Chrome and iOS versions) do not support binary data
|
||||
which is why the negotiation is necessary.
|
||||
|
||||
|
||||
### Encrypted WebSocket connections (wss://)
|
||||
|
||||
@@ -110,7 +138,7 @@ new (moved) port of the program.
|
||||
The program wrap mode is invoked by replacing the target with `--`
|
||||
followed by the program command line to wrap.
|
||||
|
||||
`./websockify 2023 -- PROGRAM ARGS`
|
||||
`./run 2023 -- PROGRAM ARGS`
|
||||
|
||||
The `--wrap-mode` option can be used to indicate what action to take
|
||||
when the wrapped program exits or daemonizes.
|
||||
@@ -119,16 +147,17 @@ Here is an example of using websockify to wrap the vncserver command
|
||||
(which backgrounds itself) for use with
|
||||
[noVNC](https://github.com/kanaka/noVNC):
|
||||
|
||||
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
||||
`./run 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
||||
|
||||
Here is an example of wrapping telnetd (from krb5-telnetd).telnetd
|
||||
Here is an example of wrapping telnetd (from krb5-telnetd). telnetd
|
||||
exits after the connection closes so the wrap mode is set to respawn
|
||||
the command:
|
||||
|
||||
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
||||
`sudo ./run 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
||||
|
||||
The `wstelnet.html` page demonstrates a simple WebSockets based telnet
|
||||
client.
|
||||
client (use 'localhost' and '2023' for the host and port
|
||||
respectively).
|
||||
|
||||
|
||||
### Building the Python ssl module (for python 2.5 and older)
|
||||
@@ -137,11 +166,10 @@ client.
|
||||
|
||||
`sudo aptitude install python-dev bluetooth-dev`
|
||||
|
||||
* Download, build the ssl module and symlink to it:
|
||||
* At the top level of the websockify repostory, download, build and
|
||||
symlink the ssl module:
|
||||
|
||||
`cd websockify/`
|
||||
|
||||
`wget http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
|
||||
`wget --no-check-certificate http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
|
||||
|
||||
`tar xvzf ssl-1.15.tar.gz`
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Installation
|
||||
---------------------------
|
||||
|
||||
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
|
||||
https://github.com/kanaka/noVNC/wiki/Compiling-Websockify-to-Windows-Executable
|
||||
https://github.com/kanaka/websockify/wiki/Compiling-Websockify-as-Windows-Executable
|
||||
|
||||
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
- Support multiple targets that are selected by the path line. Some
|
||||
sort of wildcarding of ports too.
|
||||
|
||||
- Support SSL targets too.
|
||||
|
||||
- Rust implementation
|
||||
- Add sub-protocol support to upstream einaros/ws module and use that
|
||||
instead of the patched module.
|
||||
- wstelnet: support CSI L and CSI M
|
||||
|
||||
|
||||
+9
-2
@@ -13,5 +13,12 @@ browser.
|
||||
|
||||
Building web-socket-js emulator:
|
||||
|
||||
cd include/web-socket-js/flash-src
|
||||
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as
|
||||
cd include/web-socket-js/flash-src
|
||||
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as
|
||||
|
||||
Building release tarball:
|
||||
- not really necessary since tagged revision can be downloaded
|
||||
from github as tarballs
|
||||
|
||||
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar
|
||||
gzip websockify-${WVER}.tar
|
||||
|
||||
+11
-5
@@ -1,7 +1,13 @@
|
||||
- Update setup.py and CHANGES.txt and commit
|
||||
- Update setup.py, CHANGES.txt and other/package.json and commit
|
||||
- Create version tag and tarball from tag
|
||||
WVER=0.1.0
|
||||
git tag ${WVER}
|
||||
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar
|
||||
gzip websockify-${WVER}.tar
|
||||
- Upload tarball to repo
|
||||
git tag v${WVER}
|
||||
git push origin master
|
||||
git push origin v${WVER}
|
||||
- Register with pypi.python.org (once):
|
||||
python setup.py register
|
||||
- Upload the source distribution to pypi
|
||||
python setup.py sdist upload
|
||||
- Register with npmjs.org (once)
|
||||
- Upload websockify.js npmjs.org package
|
||||
npm publish other/js
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
.TH websockify 1 "June 7, 2012" "version 0.3" "USER COMMANDS"
|
||||
|
||||
.SH NAME
|
||||
|
||||
websockify - WebSockets to TCP socket bridge
|
||||
|
||||
.SH SYNOPSIS
|
||||
|
||||
websockify [options] [source_addr:]source_port target_addr:target_port
|
||||
websockify [options] [source_addr:]source_port \-\- WRAP_COMMAND_LINE
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
-h, --help show this help message and exit
|
||||
-v, --verbose verbose messages and per frame traffic
|
||||
--record=FILE record sessions to FILE.[session_number]
|
||||
-D, --daemon become a daemon (background process)
|
||||
--run-once handle a single WebSocket connection and exit
|
||||
--timeout=TIMEOUT after TIMEOUT seconds exit when not connected
|
||||
--cert=CERT SSL certificate file
|
||||
--key=KEY SSL key file (if separate from cert)
|
||||
--ssl-only disallow non-encrypted connections
|
||||
--web=DIR run webserver on same port. Serve files from DIR.
|
||||
--wrap-mode=MODE action to take when the wrapped program exits or
|
||||
daemonizes: exit (default), ignore, respawn
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
At the most basic level, websockify just translates WebSockets traffic to normal TCP socket traffic. Websockify accepts the WebSockets handshake, parses it, and then begins forwarding traffic between the client and the target in both directions.
|
||||
|
||||
websockify was formerly named wsproxy and was part of the noVNC project.
|
||||
|
||||
.SH NOTES
|
||||
|
||||
.SS WebSockets binary data
|
||||
|
||||
Websockify supports all versions of the WebSockets protocol (Hixie and HyBI). The older Hixie versions of the protocol only support UTF-8 text payloads. In order to transport binary data over UTF-8 an encoding must used to encapsulate the data within UTF-8. Websockify uses base64 to encode all traffic to and from the client. This does not affect the data between websockify and the server.
|
||||
|
||||
.SS Encrypted WebSocket connections (wss://)
|
||||
|
||||
To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to generate a certificate for websockify to load. By default websockify loads a certificate file name self.pem but the --cert=CERT option can override the file name. You can generate a self-signed certificate using openssl. When asked for the common name, use the hostname of the server where the proxy will be running:
|
||||
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
|
||||
.SS Websock Javascript library
|
||||
|
||||
The websock.js (see https://github.com/kanaka/websockify) Javascript library library provides a Websock object that is similar to the standard WebSocket object but Websock enables communication with raw TCP sockets (i.e. the binary stream) via websockify. This is accomplished by base64 encoding the data stream between Websock and websockify.
|
||||
|
||||
Websock has built-in receive queue buffering; the message event does not contain actual data but is simply a notification that there is new data available. Several rQ* methods are available to read binary data off of the receive queue.
|
||||
|
||||
The Websock API is documented on the websock.js API wiki page:
|
||||
|
||||
https://github.com/kanaka/websockify/wiki/websock.js
|
||||
|
||||
See the "Wrap a Program" section below for an example of using Websock and websockify as a browser telnet client (wstelnet.html).
|
||||
|
||||
.SS Additional websockify features
|
||||
|
||||
These are not necessary for the basic operation.
|
||||
|
||||
.IP *
|
||||
Daemonizing: When the -D option is specified, websockify runs in the background as a daemon process.
|
||||
|
||||
.IP *
|
||||
SSL (the wss:// WebSockets URI): This is detected automatically by websockify by sniffing the first byte sent from the client and then wrapping the socket if the data starts with '\\x16' or '\\x80' (indicating SSL).
|
||||
|
||||
.IP *
|
||||
Flash security policy: websockify detects flash security policy requests (again by sniffing the first packet) and answers with an appropriate flash security policy response (and then closes the port). This means no separate flash security policy server is needed for supporting the flash WebSockets fallback emulator.
|
||||
|
||||
.IP *
|
||||
Session recording: This feature that allows recording of the traffic sent and received from the client to a file using the --record option.
|
||||
|
||||
.IP *
|
||||
Mini-webserver: websockify can detect and respond to normal web requests on the same port as the WebSockets proxy and Flash security policy. This functionality is activate with the --web DIR option where DIR is the root of the web directory to serve.
|
||||
|
||||
.IP *
|
||||
Wrap a program: see the "Wrap a Program" section below.
|
||||
|
||||
.SS Wrap a Program
|
||||
|
||||
In addition to proxying from a source address to a target address (which may be on a different system), websockify has the ability to launch a program on the local system and proxy WebSockets traffic to a normal TCP port owned/bound by the program.
|
||||
|
||||
The is accomplished with a small LD_PRELOAD library (rebind.so) which intercepts bind() system calls by the program. The specified port is moved to a new localhost/loopback free high port. websockify then proxies WebSockets traffic directed to the original port to the new (moved) port of the program.
|
||||
|
||||
The program wrap mode is invoked by replacing the target with -- followed by the program command line to wrap.
|
||||
|
||||
`./websockify 2023 -- PROGRAM ARGS`
|
||||
|
||||
The --wrap-mode option can be used to indicate what action to take when the wrapped program exits or daemonizes.
|
||||
|
||||
Here is an example of using websockify to wrap the vncserver command (which backgrounds itself) for use with noVNC:
|
||||
|
||||
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
||||
|
||||
Here is an example of wrapping telnetd (from krb5-telnetd).telnetd exits after the connection closes so the wrap mode is set to respawn the command:
|
||||
|
||||
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
||||
|
||||
The wstelnet.html page demonstrates a simple WebSockets based telnet client.
|
||||
|
||||
|
||||
.SH AUTHOR
|
||||
Joel Martin (github@martintribe.org)
|
||||
|
||||
.SH SEE ALSO
|
||||
|
||||
https://github.com/kanaka/websockify/
|
||||
|
||||
https://github.com/kanaka/websockify/wiki/
|
||||
|
||||
+32
-65
@@ -1,45 +1,8 @@
|
||||
/*
|
||||
* Modified from:
|
||||
* http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla XML-RPC Client component.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Digital Creations 2, Inc.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2000
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Martijn Pieters <mj@digicool.com> (original author)
|
||||
* Samuel Sieb <samuel@sieb.net>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
|
||||
|
||||
/*jslint white: false, bitwise: false, plusplus: false */
|
||||
/*global console */
|
||||
@@ -47,35 +10,37 @@
|
||||
var Base64 = {
|
||||
|
||||
/* Convert data (an array of integers) to a Base64 string. */
|
||||
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
|
||||
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''),
|
||||
base64Pad : '=',
|
||||
|
||||
encode: function (data) {
|
||||
"use strict";
|
||||
var result = '',
|
||||
chrTable = Base64.toBase64Table.split(''),
|
||||
pad = Base64.base64Pad,
|
||||
length = data.length,
|
||||
i;
|
||||
var result = '';
|
||||
var toBase64Table = Base64.toBase64Table;
|
||||
var base64Pad = Base64.base64Pad;
|
||||
var length = data.length;
|
||||
var i;
|
||||
// Convert every three bytes to 4 ascii characters.
|
||||
/* BEGIN LOOP */
|
||||
for (i = 0; i < (length - 2); i += 3) {
|
||||
result += chrTable[data[i] >> 2];
|
||||
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
|
||||
result += chrTable[data[i+2] & 0x3f];
|
||||
result += toBase64Table[data[i] >> 2];
|
||||
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
|
||||
result += toBase64Table[data[i+2] & 0x3f];
|
||||
}
|
||||
/* END LOOP */
|
||||
|
||||
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
|
||||
if (length%3) {
|
||||
i = length - (length%3);
|
||||
result += chrTable[data[i] >> 2];
|
||||
result += toBase64Table[data[i] >> 2];
|
||||
if ((length%3) === 2) {
|
||||
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += chrTable[(data[i+1] & 0x0f) << 2];
|
||||
result += pad;
|
||||
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += toBase64Table[(data[i+1] & 0x0f) << 2];
|
||||
result += base64Pad;
|
||||
} else {
|
||||
result += chrTable[(data[i] & 0x03) << 4];
|
||||
result += pad + pad;
|
||||
result += toBase64Table[(data[i] & 0x03) << 4];
|
||||
result += base64Pad + base64Pad;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,12 +62,12 @@ toBinaryTable : [
|
||||
decode: function (data, offset) {
|
||||
"use strict";
|
||||
offset = typeof(offset) !== 'undefined' ? offset : 0;
|
||||
var binTable = Base64.toBinaryTable,
|
||||
pad = Base64.base64Pad,
|
||||
result, result_length, idx, i, c, padding,
|
||||
leftbits = 0, // number of bits decoded, but yet to be appended
|
||||
leftdata = 0, // bits decoded, but yet to be appended
|
||||
data_length = data.indexOf('=') - offset;
|
||||
var toBinaryTable = Base64.toBinaryTable;
|
||||
var base64Pad = Base64.base64Pad;
|
||||
var result, result_length, idx, i, c, padding;
|
||||
var leftbits = 0; // number of bits decoded, but yet to be appended
|
||||
var leftdata = 0; // bits decoded, but yet to be appended
|
||||
var data_length = data.indexOf('=') - offset;
|
||||
|
||||
if (data_length < 0) { data_length = data.length - offset; }
|
||||
|
||||
@@ -111,9 +76,10 @@ decode: function (data, offset) {
|
||||
result = new Array(result_length);
|
||||
|
||||
// Convert one by one.
|
||||
/* BEGIN LOOP */
|
||||
for (idx = 0, i = offset; i < data.length; i++) {
|
||||
c = binTable[data.charCodeAt(i) & 0x7f];
|
||||
padding = (data.charAt(i) === pad);
|
||||
c = toBinaryTable[data.charCodeAt(i) & 0x7f];
|
||||
padding = (data.charAt(i) === base64Pad);
|
||||
// Skip illegal characters and whitespace
|
||||
if (c === -1) {
|
||||
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
|
||||
@@ -134,6 +100,7 @@ decode: function (data, offset) {
|
||||
leftdata &= (1 << leftbits) - 1;
|
||||
}
|
||||
}
|
||||
/* END LOOP */
|
||||
|
||||
// If there are any bits left, the base64 string was corrupted
|
||||
if (leftbits) {
|
||||
|
||||
+79
-3
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
* from noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
@@ -57,6 +57,21 @@ if (!Array.prototype.map)
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// requestAnimationFrame shim with setTimeout fallback
|
||||
//
|
||||
|
||||
window.requestAnimFrame = (function(){
|
||||
return window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback){
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
})();
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------
|
||||
* Namespaced in Util
|
||||
@@ -131,6 +146,8 @@ Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
|
||||
}
|
||||
} else if (type in {'integer':1, 'int':1}) {
|
||||
val = parseInt(val, 10);
|
||||
} else if (type === 'str') {
|
||||
val = String(val);
|
||||
} else if (type === 'func') {
|
||||
if (!val) {
|
||||
val = function () {};
|
||||
@@ -190,6 +207,65 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
|
||||
* Cross-browser routines
|
||||
*/
|
||||
|
||||
|
||||
// Dynamically load scripts without using document.write()
|
||||
// Reference: http://unixpapa.com/js/dyna.html
|
||||
//
|
||||
// Handles the case where load_scripts is invoked from a script that
|
||||
// itself is loaded via load_scripts. Once all scripts are loaded the
|
||||
// window.onscriptsloaded handler is called (if set).
|
||||
Util.get_include_uri = function() {
|
||||
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
|
||||
}
|
||||
Util._loading_scripts = [];
|
||||
Util._pending_scripts = [];
|
||||
Util.load_scripts = function(files) {
|
||||
var head = document.getElementsByTagName('head')[0], script,
|
||||
ls = Util._loading_scripts, ps = Util._pending_scripts;
|
||||
for (var f=0; f<files.length; f++) {
|
||||
script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = Util.get_include_uri() + files[f];
|
||||
//console.log("loading script: " + script.src);
|
||||
script.onload = script.onreadystatechange = function (e) {
|
||||
while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
|
||||
ls[0].readyState === 'complete')) {
|
||||
// For IE, append the script to trigger execution
|
||||
var s = ls.shift();
|
||||
//console.log("loaded script: " + s.src);
|
||||
head.appendChild(s);
|
||||
}
|
||||
if (!this.readyState ||
|
||||
(Util.Engine.presto && this.readyState === 'loaded') ||
|
||||
this.readyState === 'complete') {
|
||||
if (ps.indexOf(this) >= 0) {
|
||||
this.onload = this.onreadystatechange = null;
|
||||
//console.log("completed script: " + this.src);
|
||||
ps.splice(ps.indexOf(this), 1);
|
||||
|
||||
// Call window.onscriptsload after last script loads
|
||||
if (ps.length === 0 && window.onscriptsload) {
|
||||
window.onscriptsload();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// In-order script execution tricks
|
||||
if (Util.Engine.trident) {
|
||||
// For IE wait until readyState is 'loaded' before
|
||||
// appending it which will trigger execution
|
||||
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
|
||||
ls.push(script);
|
||||
} else {
|
||||
// For webkit and firefox set async=false and append now
|
||||
// https://developer.mozilla.org/en-US/docs/HTML/Element/script
|
||||
script.async = false;
|
||||
head.appendChild(script);
|
||||
}
|
||||
ps.push(script);
|
||||
}
|
||||
}
|
||||
|
||||
// Get DOM element position on page
|
||||
Util.getPosition = function (obj) {
|
||||
var x = 0, y = 0;
|
||||
|
||||
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/>
|
||||
// License: New BSD License
|
||||
// Reference: http://dev.w3.org/html5/websockets/
|
||||
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
|
||||
// Reference: http://tools.ietf.org/html/rfc6455
|
||||
|
||||
(function() {
|
||||
|
||||
if (window.WebSocket) return;
|
||||
|
||||
var console = window.console;
|
||||
if (!console || !console.log || !console.error) {
|
||||
console = {log: function(){ }, error: function(){ }};
|
||||
if (window.WEB_SOCKET_FORCE_FLASH) {
|
||||
// Keeps going.
|
||||
} else if (window.WebSocket) {
|
||||
return;
|
||||
} else if (window.MozWebSocket) {
|
||||
// Firefox.
|
||||
window.WebSocket = MozWebSocket;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
|
||||
console.error("Flash Player >= 10.0.0 is required.");
|
||||
var logger;
|
||||
if (window.WEB_SOCKET_LOGGER) {
|
||||
logger = WEB_SOCKET_LOGGER;
|
||||
} else if (window.console && window.console.log && window.console.error) {
|
||||
// In some environment, console is defined but console.log or console.error is missing.
|
||||
logger = window.console;
|
||||
} else {
|
||||
logger = {log: function(){ }, error: function(){ }};
|
||||
}
|
||||
|
||||
// swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
|
||||
if (swfobject.getFlashPlayerVersion().major < 10) {
|
||||
logger.error("Flash Player >= 10.0.0 is required.");
|
||||
return;
|
||||
}
|
||||
if (location.protocol == "file:") {
|
||||
console.error(
|
||||
logger.error(
|
||||
"WARNING: web-socket-js doesn't work in file:///... URL " +
|
||||
"unless you set Flash Security Settings properly. " +
|
||||
"Open the page via Web server i.e. http://...");
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a faux web socket.
|
||||
* Our own implementation of WebSocket class using Flash.
|
||||
* @param {string} url
|
||||
* @param {string} protocol
|
||||
* @param {array or string} protocols
|
||||
* @param {string} proxyHost
|
||||
* @param {int} proxyPort
|
||||
* @param {string} headers
|
||||
*/
|
||||
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
|
||||
window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
|
||||
var self = this;
|
||||
self.__id = WebSocket.__nextId++;
|
||||
WebSocket.__instances[self.__id] = self;
|
||||
self.readyState = WebSocket.CONNECTING;
|
||||
self.bufferedAmount = 0;
|
||||
self.__events = {};
|
||||
if (!protocols) {
|
||||
protocols = [];
|
||||
} else if (typeof protocols == "string") {
|
||||
protocols = [protocols];
|
||||
}
|
||||
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
|
||||
// Otherwise, when onopen fires immediately, onopen is called before it is set.
|
||||
setTimeout(function() {
|
||||
self.__createTask = setTimeout(function() {
|
||||
WebSocket.__addTask(function() {
|
||||
self.__createTask = null;
|
||||
WebSocket.__flash.create(
|
||||
self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null);
|
||||
self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
@@ -78,6 +98,12 @@
|
||||
* Close this web socket gracefully.
|
||||
*/
|
||||
WebSocket.prototype.close = function() {
|
||||
if (this.__createTask) {
|
||||
clearTimeout(this.__createTask);
|
||||
this.__createTask = null;
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
return;
|
||||
}
|
||||
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
|
||||
return;
|
||||
}
|
||||
@@ -131,7 +157,7 @@
|
||||
events[i](event);
|
||||
}
|
||||
var handler = this["on" + event.type];
|
||||
if (handler) handler(event);
|
||||
if (handler) handler.apply(this, [event]);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -139,16 +165,22 @@
|
||||
* @param {Object} flashEvent
|
||||
*/
|
||||
WebSocket.prototype.__handleEvent = function(flashEvent) {
|
||||
|
||||
if ("readyState" in flashEvent) {
|
||||
this.readyState = flashEvent.readyState;
|
||||
}
|
||||
if ("protocol" in flashEvent) {
|
||||
this.protocol = flashEvent.protocol;
|
||||
}
|
||||
|
||||
var jsEvent;
|
||||
if (flashEvent.type == "open" || flashEvent.type == "error") {
|
||||
jsEvent = this.__createSimpleEvent(flashEvent.type);
|
||||
} else if (flashEvent.type == "close") {
|
||||
// TODO implement jsEvent.wasClean
|
||||
jsEvent = this.__createSimpleEvent("close");
|
||||
jsEvent.wasClean = flashEvent.wasClean ? true : false;
|
||||
jsEvent.code = flashEvent.code;
|
||||
jsEvent.reason = flashEvent.reason;
|
||||
} else if (flashEvent.type == "message") {
|
||||
var data = decodeURIComponent(flashEvent.message);
|
||||
jsEvent = this.__createMessageEvent("message", data);
|
||||
@@ -157,6 +189,7 @@
|
||||
}
|
||||
|
||||
this.dispatchEvent(jsEvent);
|
||||
|
||||
};
|
||||
|
||||
WebSocket.prototype.__createSimpleEvent = function(type) {
|
||||
@@ -188,6 +221,9 @@
|
||||
WebSocket.CLOSING = 2;
|
||||
WebSocket.CLOSED = 3;
|
||||
|
||||
// Field to check implementation of WebSocket.
|
||||
WebSocket.__isFlashImplementation = true;
|
||||
WebSocket.__initialized = false;
|
||||
WebSocket.__flash = null;
|
||||
WebSocket.__instances = {};
|
||||
WebSocket.__tasks = [];
|
||||
@@ -207,16 +243,31 @@
|
||||
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
|
||||
*/
|
||||
WebSocket.__initialize = function() {
|
||||
if (WebSocket.__flash) return;
|
||||
|
||||
if (WebSocket.__initialized) return;
|
||||
WebSocket.__initialized = true;
|
||||
|
||||
if (WebSocket.__swfLocation) {
|
||||
// For backword compatibility.
|
||||
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
|
||||
}
|
||||
if (!window.WEB_SOCKET_SWF_LOCATION) {
|
||||
console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
||||
logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
||||
return;
|
||||
}
|
||||
if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
|
||||
!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
|
||||
WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
|
||||
var swfHost = RegExp.$1;
|
||||
if (location.host != swfHost) {
|
||||
logger.error(
|
||||
"[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
|
||||
"('" + location.host + "' != '" + swfHost + "'). " +
|
||||
"See also 'How to host HTML file and SWF file in different domains' section " +
|
||||
"in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
|
||||
"by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
|
||||
}
|
||||
}
|
||||
var container = document.createElement("div");
|
||||
container.id = "webSocketContainer";
|
||||
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
|
||||
@@ -250,9 +301,11 @@
|
||||
null,
|
||||
function(e) {
|
||||
if (!e.success) {
|
||||
console.error("[WebSocket] swfobject.embedSWF failed");
|
||||
logger.error("[WebSocket] swfobject.embedSWF failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -287,7 +340,7 @@
|
||||
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logger.error(e);
|
||||
}
|
||||
}, 0);
|
||||
return true;
|
||||
@@ -295,12 +348,12 @@
|
||||
|
||||
// Called by Flash.
|
||||
WebSocket.__log = function(message) {
|
||||
console.log(decodeURIComponent(message));
|
||||
logger.log(decodeURIComponent(message));
|
||||
};
|
||||
|
||||
// Called by Flash.
|
||||
WebSocket.__error = function(message) {
|
||||
console.error(decodeURIComponent(message));
|
||||
logger.error(decodeURIComponent(message));
|
||||
};
|
||||
|
||||
WebSocket.__addTask = function(task) {
|
||||
@@ -327,15 +380,12 @@
|
||||
};
|
||||
|
||||
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener("load", function(){
|
||||
WebSocket.__initialize();
|
||||
}, false);
|
||||
} else {
|
||||
window.attachEvent("onload", function(){
|
||||
WebSocket.__initialize();
|
||||
});
|
||||
}
|
||||
// NOTE:
|
||||
// This fires immediately if web_socket.js is dynamically loaded after
|
||||
// the document is loaded.
|
||||
swfobject.addDomLoadEvent(function() {
|
||||
WebSocket.__initialize();
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
+102
-33
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Websock: high-performance binary WebSockets
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* Websock is similar to the standard WebSocket object but Websock
|
||||
* enables communication with raw TCP sockets (i.e. the binary stream)
|
||||
@@ -14,9 +14,17 @@
|
||||
* read binary data off of the receive queue.
|
||||
*/
|
||||
|
||||
/*jslint browser: true, bitwise: false, plusplus: false */
|
||||
/*global Util, Base64 */
|
||||
|
||||
|
||||
// Load Flash WebSocket emulator if needed
|
||||
|
||||
// To force WebSocket emulator even when native WebSocket available
|
||||
//window.WEB_SOCKET_FORCE_FLASH = true;
|
||||
// To enable WebSocket emulator debug:
|
||||
//window.WEB_SOCKET_DEBUG=1;
|
||||
|
||||
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
Websock_native = true;
|
||||
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
@@ -25,28 +33,16 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
} else {
|
||||
/* no builtin WebSocket so load web_socket.js */
|
||||
|
||||
// To enable debug:
|
||||
// window.WEB_SOCKET_DEBUG=1;
|
||||
|
||||
Websock_native = false;
|
||||
(function () {
|
||||
function get_INCLUDE_URI() {
|
||||
return (typeof INCLUDE_URI !== "undefined") ?
|
||||
INCLUDE_URI : "include/";
|
||||
}
|
||||
|
||||
var start = "<script src='" + get_INCLUDE_URI(),
|
||||
end = "'><\/script>", extra = "";
|
||||
|
||||
WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
|
||||
window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
|
||||
"web-socket-js/WebSocketMain.swf";
|
||||
if (Util.Engine.trident) {
|
||||
Util.Debug("Forcing uncached load of WebSocketMain.swf");
|
||||
WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
||||
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
||||
}
|
||||
extra += start + "web-socket-js/swfobject.js" + end;
|
||||
extra += start + "web-socket-js/web_socket.js" + end;
|
||||
document.write(extra);
|
||||
Util.load_scripts(["web-socket-js/swfobject.js",
|
||||
"web-socket-js/web_socket.js"]);
|
||||
}());
|
||||
}
|
||||
|
||||
@@ -56,6 +52,7 @@ function Websock() {
|
||||
|
||||
var api = {}, // Public API
|
||||
websocket = null, // WebSocket object
|
||||
mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
|
||||
rQ = [], // Receive queue
|
||||
rQi = 0, // Receive queue index
|
||||
rQmax = 10000, // Max receive queue size before compacting
|
||||
@@ -87,7 +84,7 @@ function get_rQi() {
|
||||
}
|
||||
function set_rQi(val) {
|
||||
rQi = val;
|
||||
};
|
||||
}
|
||||
|
||||
function rQlen() {
|
||||
return rQ.length - rQi;
|
||||
@@ -122,9 +119,7 @@ function rQshiftStr(len) {
|
||||
if (typeof(len) === 'undefined') { len = rQlen(); }
|
||||
var arr = rQ.slice(rQi, rQi + len);
|
||||
rQi += len;
|
||||
return arr.map(function (num) {
|
||||
return String.fromCharCode(num); } ).join('');
|
||||
|
||||
return String.fromCharCode.apply(null, arr);
|
||||
}
|
||||
function rQshiftBytes(len) {
|
||||
if (typeof(len) === 'undefined') { len = rQlen(); }
|
||||
@@ -164,14 +159,27 @@ function rQwait(msg, num, goback) {
|
||||
//
|
||||
|
||||
function encode_message() {
|
||||
/* base64 encode */
|
||||
return Base64.encode(sQ);
|
||||
if (mode === 'binary') {
|
||||
// Put in a binary arraybuffer
|
||||
return (new Uint8Array(sQ)).buffer;
|
||||
} else {
|
||||
// base64 encode
|
||||
return Base64.encode(sQ);
|
||||
}
|
||||
}
|
||||
|
||||
function decode_message(data) {
|
||||
//Util.Debug(">> decode_message: " + data);
|
||||
/* base64 decode */
|
||||
rQ = rQ.concat(Base64.decode(data, 0));
|
||||
if (mode === 'binary') {
|
||||
// push arraybuffer values onto the end
|
||||
var u8 = new Uint8Array(data);
|
||||
for (var i = 0; i < u8.length; i++) {
|
||||
rQ.push(u8[i]);
|
||||
}
|
||||
} else {
|
||||
// base64 decode and concat to the end
|
||||
rQ = rQ.concat(Base64.decode(data, 0));
|
||||
}
|
||||
//Util.Debug(">> decode_message, rQ: " + rQ);
|
||||
}
|
||||
|
||||
@@ -254,29 +262,89 @@ function on(evt, handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
|
||||
function init() {
|
||||
function init(protocols) {
|
||||
rQ = [];
|
||||
rQi = 0;
|
||||
sQ = [];
|
||||
websocket = null;
|
||||
|
||||
var bt = false,
|
||||
wsbt = false,
|
||||
try_binary = false;
|
||||
|
||||
// Check for full typed array support
|
||||
if (('Uint8Array' in window) &&
|
||||
('set' in Uint8Array.prototype)) {
|
||||
bt = true;
|
||||
}
|
||||
|
||||
// Check for full binary type support in WebSockets
|
||||
// TODO: this sucks, the property should exist on the prototype
|
||||
// but it does not.
|
||||
try {
|
||||
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
|
||||
Util.Info("Detected binaryType support in WebSockets");
|
||||
wsbt = true;
|
||||
}
|
||||
} catch (exc) {
|
||||
// Just ignore failed test localhost connections
|
||||
}
|
||||
|
||||
// Default protocols if not specified
|
||||
if (typeof(protocols) === "undefined") {
|
||||
if (wsbt) {
|
||||
protocols = ['binary', 'base64'];
|
||||
} else {
|
||||
protocols = 'base64';
|
||||
}
|
||||
}
|
||||
|
||||
// If no binary support, make sure it was not requested
|
||||
if (!wsbt) {
|
||||
if (protocols === 'binary') {
|
||||
throw("WebSocket binary sub-protocol requested but not supported");
|
||||
}
|
||||
if (typeof(protocols) === "object") {
|
||||
var new_protocols = [];
|
||||
for (var i = 0; i < protocols.length; i++) {
|
||||
if (protocols[i] === 'binary') {
|
||||
Util.Error("Skipping unsupported WebSocket binary sub-protocol");
|
||||
} else {
|
||||
new_protocols.push(protocols[i]);
|
||||
}
|
||||
}
|
||||
if (new_protocols.length > 0) {
|
||||
protocols = new_protocols;
|
||||
} else {
|
||||
throw("Only WebSocket binary sub-protocol was requested and not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return protocols;
|
||||
}
|
||||
|
||||
function open(uri) {
|
||||
init();
|
||||
function open(uri, protocols) {
|
||||
protocols = init(protocols);
|
||||
|
||||
if (test_mode) {
|
||||
websocket = {};
|
||||
} else {
|
||||
websocket = new WebSocket(uri, 'base64');
|
||||
// TODO: future native binary support
|
||||
//websocket = new WebSocket(uri, ['binary', 'base64']);
|
||||
websocket = new WebSocket(uri, protocols);
|
||||
if (protocols.indexOf('binary') >= 0) {
|
||||
websocket.binaryType = 'arraybuffer';
|
||||
}
|
||||
}
|
||||
|
||||
websocket.onmessage = recv_message;
|
||||
websocket.onopen = function() {
|
||||
Util.Debug(">> WebSock.onopen");
|
||||
if (websocket.protocol) {
|
||||
mode = websocket.protocol;
|
||||
Util.Info("Server chose sub-protocol: " + websocket.protocol);
|
||||
} else {
|
||||
mode = 'base64';
|
||||
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
|
||||
}
|
||||
eventHandlers.open();
|
||||
Util.Debug("<< WebSock.onopen");
|
||||
@@ -306,8 +374,9 @@ function close() {
|
||||
|
||||
// Override internal functions for testing
|
||||
// Takes a send function, returns reference to recv function
|
||||
function testMode(override_send) {
|
||||
function testMode(override_send, data_mode) {
|
||||
test_mode = true;
|
||||
mode = data_mode;
|
||||
api.send = override_send;
|
||||
api.close = function () {};
|
||||
return recv_message;
|
||||
|
||||
+78
-10
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* from noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2010 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
@@ -37,14 +37,16 @@ if (!window.$D) {
|
||||
*/
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
WebUtil.init_logging = function() {
|
||||
Util._log_level = (document.location.href.match(
|
||||
/logging=([A-Za-z0-9\._\-]*)/) ||
|
||||
['', Util._log_level])[1];
|
||||
|
||||
WebUtil.init_logging = function(level) {
|
||||
if (typeof level !== "undefined") {
|
||||
Util._log_level = level;
|
||||
} else {
|
||||
Util._log_level = (document.location.href.match(
|
||||
/logging=([A-Za-z0-9\._\-]*)/) ||
|
||||
['', Util._log_level])[1];
|
||||
}
|
||||
Util.init_logging();
|
||||
};
|
||||
WebUtil.init_logging();
|
||||
|
||||
|
||||
WebUtil.dirObj = function (obj, depth, parent) {
|
||||
@@ -75,9 +77,14 @@ WebUtil.dirObj = function (obj, depth, parent) {
|
||||
|
||||
// Read a query string variable
|
||||
WebUtil.getQueryVar = function(name, defVal) {
|
||||
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)');
|
||||
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'),
|
||||
match = document.location.href.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
return (document.location.href.match(re) || ['',defVal])[1];
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]);
|
||||
} else {
|
||||
return defVal;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -113,6 +120,67 @@ WebUtil.eraseCookie = function(name) {
|
||||
WebUtil.createCookie(name,"",-1);
|
||||
};
|
||||
|
||||
/*
|
||||
* Setting handling.
|
||||
*/
|
||||
|
||||
WebUtil.initSettings = function(callback) {
|
||||
var callbackArgs = Array.prototype.slice.call(arguments, 1);
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.get(function (cfg) {
|
||||
WebUtil.settings = cfg;
|
||||
console.log(WebUtil.settings);
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No-op
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// No days means only for this browser session
|
||||
WebUtil.writeSetting = function(name, value) {
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
//console.log("writeSetting:", name, value);
|
||||
if (WebUtil.settings[name] !== value) {
|
||||
WebUtil.settings[name] = value;
|
||||
window.chrome.storage.sync.set(WebUtil.settings);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem(name, value);
|
||||
}
|
||||
};
|
||||
|
||||
WebUtil.readSetting = function(name, defaultValue) {
|
||||
var value;
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
value = WebUtil.settings[name];
|
||||
} else {
|
||||
value = localStorage.getItem(name);
|
||||
}
|
||||
if (typeof value === "undefined") {
|
||||
value = null;
|
||||
}
|
||||
if (value === null && typeof defaultValue !== undefined) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
WebUtil.eraseSetting = function(name) {
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.remove(name);
|
||||
delete WebUtil.settings[name];
|
||||
} else {
|
||||
localStorage.removeItem(name);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Alternate stylesheet selection
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
// Copyright 2012 Joel Martin
|
||||
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
// Known to work with node 0.6
|
||||
// Requires node modules: ws, base64, optimist and policyfile
|
||||
// npm install ws base64 optimist policyfile
|
||||
// Known to work with node 0.8.9
|
||||
// Requires node modules: ws, optimist and policyfile
|
||||
// npm install ws optimist policyfile
|
||||
|
||||
|
||||
var argv = require('optimist').argv,
|
||||
net = require('net'),
|
||||
http = require('http'),
|
||||
https = require('https'),
|
||||
url = require('url'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
policyfile = require('policyfile'),
|
||||
|
||||
base64 = require('base64/build/Release/base64'),
|
||||
Buffer = require('buffer').Buffer,
|
||||
WebSocketServer = require('ws').Server,
|
||||
|
||||
httpServer, wsServer,
|
||||
webServer, wsServer,
|
||||
source_host, source_port, target_host, target_port,
|
||||
web_path = null;
|
||||
|
||||
|
||||
// Handle new WebSocket client
|
||||
new_client = function(client) {
|
||||
console.log('WebSocket client connected');
|
||||
//console.log('protocol: ' + client.protocol);
|
||||
var clientAddr = client._socket.remoteAddress, log;
|
||||
console.log(client.upgradeReq.url);
|
||||
log = function (msg) {
|
||||
console.log(' ' + clientAddr + ': '+ msg);
|
||||
};
|
||||
log('WebSocket connection');
|
||||
log('Version ' + client.protocolVersion + ', subprotocol: ' + client.protocol);
|
||||
|
||||
var target = net.createConnection(target_port,target_host);
|
||||
target.on('begin', function() {
|
||||
console.log('connected to target');
|
||||
var target = net.createConnection(target_port,target_host, function() {
|
||||
log('connected to target');
|
||||
});
|
||||
target.on('data', function(data) {
|
||||
client.send(base64.encode(new Buffer(data)));
|
||||
//log("sending message: " + data);
|
||||
try {
|
||||
if (client.protocol === 'base64') {
|
||||
client.send(new Buffer(data).toString('base64'));
|
||||
} else {
|
||||
client.send(data,{binary: true});
|
||||
}
|
||||
} catch(e) {
|
||||
log("Client closed, cleaning up target");
|
||||
target.end();
|
||||
}
|
||||
});
|
||||
target.on('end', function() {
|
||||
console.log('target disconnected');
|
||||
log('target disconnected');
|
||||
client.close();
|
||||
});
|
||||
target.on('error', function() {
|
||||
log('target connection error');
|
||||
target.end();
|
||||
client.close();
|
||||
});
|
||||
|
||||
client.on('message', function(msg) {
|
||||
//console.log('got some message');
|
||||
target.write(base64.decode(msg),'binary');
|
||||
//log('got message: ' + msg);
|
||||
if (client.protocol === 'base64') {
|
||||
target.write(new Buffer(msg, 'base64'));
|
||||
} else {
|
||||
target.write(msg,'binary');
|
||||
}
|
||||
});
|
||||
client.on('close', function(code, reason) {
|
||||
console.log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
|
||||
log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
|
||||
target.end();
|
||||
});
|
||||
client.on('error', function(a) {
|
||||
console.log('WebSocket client error: ' + a);
|
||||
log('WebSocket client error: ' + a);
|
||||
target.end();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -73,7 +102,7 @@ http_request = function (request, response) {
|
||||
var uri = url.parse(request.url).pathname
|
||||
, filename = path.join(argv.web, uri);
|
||||
|
||||
path.exists(filename, function(exists) {
|
||||
fs.exists(filename, function(exists) {
|
||||
if(!exists) {
|
||||
return http_error(response, 404, "404 Not Found");
|
||||
}
|
||||
@@ -94,10 +123,23 @@ http_request = function (request, response) {
|
||||
});
|
||||
};
|
||||
|
||||
// Select 'binary' or 'base64' subprotocol, preferring 'binary'
|
||||
selectProtocol = function(protocols, callback) {
|
||||
if (protocols.indexOf('binary') >= 0) {
|
||||
callback(true, 'binary');
|
||||
} else if (protocols.indexOf('base64') >= 0) {
|
||||
callback(true, 'base64');
|
||||
} else {
|
||||
console.log("Client must support 'binary' or 'base64' protocol");
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
|
||||
// parse source and target arguments into parts
|
||||
source_arg = argv._[0].toString();
|
||||
target_arg = argv._[1].toString();
|
||||
try {
|
||||
source_arg = argv._[0].toString();
|
||||
target_arg = argv._[1].toString();
|
||||
|
||||
var idx;
|
||||
idx = source_arg.indexOf(":");
|
||||
if (idx >= 0) {
|
||||
@@ -119,7 +161,7 @@ try {
|
||||
throw("illegal port");
|
||||
}
|
||||
} catch(e) {
|
||||
console.error("wsproxy.py [source_addr:]source_port target_addr:target_port");
|
||||
console.error("websockify.js [--web web_dir] [--cert cert.pem [--key key.pem]] [source_addr:]source_port target_addr:target_port");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
@@ -130,11 +172,21 @@ if (argv.web) {
|
||||
console.log(" - Web server active. Serving: " + argv.web);
|
||||
}
|
||||
|
||||
httpServer = http.createServer(http_request);
|
||||
httpServer.listen(source_port, function() {
|
||||
wsServer = new WebSocketServer({server: httpServer});
|
||||
if (argv.cert) {
|
||||
argv.key = argv.key || argv.cert;
|
||||
var cert = fs.readFileSync(argv.cert),
|
||||
key = fs.readFileSync(argv.key);
|
||||
console.log(" - Running in encrypted HTTPS (wss://) mode using: " + argv.cert + ", " + argv.key);
|
||||
webServer = https.createServer({cert: cert, key: key}, http_request);
|
||||
} else {
|
||||
console.log(" - Running in unencrypted HTTP (ws://) mode");
|
||||
webServer = http.createServer(http_request);
|
||||
}
|
||||
webServer.listen(source_port, function() {
|
||||
wsServer = new WebSocketServer({server: webServer,
|
||||
handleProtocols: selectProtocol});
|
||||
wsServer.on('connection', new_client);
|
||||
});
|
||||
|
||||
// Attach Flash policyfile answer service
|
||||
policyfile.createServer().listen(-1, httpServer);
|
||||
policyfile.createServer().listen(-1, webServer);
|
||||
+14
-4
@@ -16,6 +16,8 @@ usage() {
|
||||
echo " Default: localhost:5900"
|
||||
echo " --cert CERT Path to combined cert/key file"
|
||||
echo " Default: self.pem"
|
||||
echo " --web WEB Path to web files (e.g. vnc.html)"
|
||||
echo " Default: ./"
|
||||
exit 2
|
||||
}
|
||||
|
||||
@@ -24,6 +26,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
PORT="6080"
|
||||
VNC_DEST="localhost:5900"
|
||||
CERT=""
|
||||
WEB=""
|
||||
proxy_pid=""
|
||||
|
||||
die() {
|
||||
@@ -50,6 +53,7 @@ while [ "$*" ]; do
|
||||
--listen) PORT="${OPTARG}"; shift ;;
|
||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||
--cert) CERT="${OPTARG}"; shift ;;
|
||||
--web) WEB="${OPTARG}"; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||
*) break ;;
|
||||
@@ -60,18 +64,24 @@ done
|
||||
which netstat >/dev/null 2>&1 \
|
||||
|| die "Must have netstat installed"
|
||||
|
||||
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
|
||||
netstat -ltn | grep -qs "${PORT} .*LISTEN" \
|
||||
&& die "Port ${PORT} in use. Try --listen PORT"
|
||||
|
||||
trap "cleanup" TERM QUIT INT EXIT
|
||||
|
||||
# Find vnc.html
|
||||
if [ -e "$(pwd)/vnc.html" ]; then
|
||||
if [ -n "${WEB}" ]; then
|
||||
if [ ! -e "${WEB}/vnc.html" ]; then
|
||||
die "Could not find ${WEB}/vnc.html"
|
||||
fi
|
||||
elif [ -e "$(pwd)/vnc.html" ]; then
|
||||
WEB=$(pwd)
|
||||
elif [ -e "${HERE}/../vnc.html" ]; then
|
||||
WEB=${HERE}/../
|
||||
elif [ -e "${HERE}/vnc.html" ]; then
|
||||
WEB=${HERE}
|
||||
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
|
||||
WEB=${HERE}/../share/novnc/
|
||||
else
|
||||
die "Could not find vnc.html"
|
||||
fi
|
||||
@@ -92,7 +102,7 @@ else
|
||||
fi
|
||||
|
||||
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||
${HERE}/wsproxy.py --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
proxy_pid="$!"
|
||||
sleep 1
|
||||
if ! ps -p ${proxy_pid} >/dev/null; then
|
||||
@@ -101,7 +111,7 @@ if ! ps -p ${proxy_pid} >/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "\n\nNavigate to to this URL:\n"
|
||||
echo -e "\n\nNavigate to this URL:\n"
|
||||
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||
echo -e "Press Ctrl-C to exit\n\n"
|
||||
|
||||
|
||||
+1
-1
@@ -795,7 +795,7 @@ void start_server() {
|
||||
}
|
||||
handler_msg("handler exit\n");
|
||||
} else {
|
||||
handler_msg("wsproxy exit\n");
|
||||
handler_msg("websockify exit\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+61
-24
@@ -9,11 +9,22 @@
|
||||
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
||||
|
||||
require 'gserver'
|
||||
require 'openssl'
|
||||
require 'stringio'
|
||||
require 'digest/md5'
|
||||
require 'digest/sha1'
|
||||
require 'base64'
|
||||
|
||||
unless OpenSSL::SSL::SSLSocket.instance_methods.index("read_nonblock")
|
||||
module OpenSSL
|
||||
module SSL
|
||||
class SSLSocket
|
||||
alias :read_nonblock :readpartial
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class EClose < Exception
|
||||
end
|
||||
|
||||
@@ -44,7 +55,17 @@ Sec-WebSocket-Accept: %s\r
|
||||
host = opts['listen_host'] || GServer::DEFAULT_HOST
|
||||
|
||||
super(port, host)
|
||||
|
||||
msg opts.inspect
|
||||
if opts['server_cert']
|
||||
msg "creating ssl context"
|
||||
@sslContext = OpenSSL::SSL::SSLContext.new
|
||||
@sslContext.cert = OpenSSL::X509::Certificate.new(File.open(opts['server_cert']))
|
||||
@sslContext.key = OpenSSL::PKey::RSA.new(File.open(opts['server_key']))
|
||||
@sslContext.ca_file = opts['server_cert']
|
||||
@sslContext.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
@sslContext.verify_depth = 0
|
||||
end
|
||||
|
||||
@@client_id = 0 # Track client number total on class
|
||||
|
||||
@verbose = opts['verbose']
|
||||
@@ -53,6 +74,18 @@ Sec-WebSocket-Accept: %s\r
|
||||
|
||||
def serve(io)
|
||||
@@client_id += 1
|
||||
msg self.inspect
|
||||
if @sslContext
|
||||
msg "Enabling SSL context"
|
||||
ssl = OpenSSL::SSL::SSLSocket.new(io, @sslContext)
|
||||
#ssl.sync_close = true
|
||||
#ssl.sync = true
|
||||
msg "SSL accepting"
|
||||
ssl.accept
|
||||
io = ssl # replace the unencrypted handle with the encrypted one
|
||||
end
|
||||
|
||||
msg "initializing thread"
|
||||
|
||||
# Initialize per thread state
|
||||
t = Thread.current
|
||||
@@ -61,11 +94,11 @@ Sec-WebSocket-Accept: %s\r
|
||||
t[:recv_part] = nil
|
||||
t[:base64] = nil
|
||||
|
||||
puts "in serve, client: #{t[:client].inspect}"
|
||||
puts "in serve, client: #{t[:my_client_id].inspect}"
|
||||
|
||||
begin
|
||||
t[:client] = do_handshake(io)
|
||||
new_client(t[:client])
|
||||
new_websocket_client(t[:client])
|
||||
rescue EClose => e
|
||||
msg "Client closed: #{e.message}"
|
||||
return
|
||||
@@ -85,12 +118,12 @@ Sec-WebSocket-Accept: %s\r
|
||||
if @verbose then print token; STDOUT.flush; end
|
||||
end
|
||||
|
||||
def msg(msg)
|
||||
puts "% 3d: %s" % [Thread.current[:my_client_id], msg]
|
||||
def msg(m)
|
||||
printf("% 3d: %s\n", Thread.current[:my_client_id] || 0, m)
|
||||
end
|
||||
|
||||
def vmsg(msg)
|
||||
if @verbose then msg(msg) end
|
||||
def vmsg(m)
|
||||
if @verbose then msg(m) end
|
||||
end
|
||||
|
||||
#
|
||||
@@ -110,12 +143,11 @@ Sec-WebSocket-Accept: %s\r
|
||||
|
||||
def unmask(buf, hlen, length)
|
||||
pstart = hlen + 4
|
||||
mask = buf[hlen...hlen+4]
|
||||
mask = buf[hlen...hlen+4].each_byte.map{|b|b}
|
||||
data = buf[pstart...pstart+length]
|
||||
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
|
||||
for i in (0...data.length) do
|
||||
data[i] ^= mask[i%4]
|
||||
end
|
||||
i=-1
|
||||
data = data.each_byte.map{|b| i+=1; (b ^ mask[i % 4]).chr}.join("")
|
||||
return data
|
||||
end
|
||||
|
||||
@@ -237,7 +269,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
|
||||
while t[:send_parts].length > 0
|
||||
buf = t[:send_parts].shift
|
||||
sent = t[:client].send(buf, 0)
|
||||
sent = t[:client].write(buf)
|
||||
|
||||
if sent == buf.length
|
||||
traffic "<"
|
||||
@@ -257,7 +289,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
closed = false
|
||||
bufs = []
|
||||
|
||||
buf = t[:client].recv(@@Buffer_size)
|
||||
buf = t[:client].read_nonblock(@@Buffer_size)
|
||||
|
||||
if buf.length == 0
|
||||
return bufs, "Client closed abrubtly"
|
||||
@@ -286,10 +318,10 @@ Sec-WebSocket-Accept: %s\r
|
||||
end
|
||||
end
|
||||
else
|
||||
if buf[0...2] == "\xff\x00":
|
||||
if buf[0...2] == "\xff\x00"
|
||||
closed = "Client sent orderly close frame"
|
||||
break
|
||||
elsif buf[0...2] == "\x00\xff":
|
||||
elsif buf[0...2] == "\x00\xff"
|
||||
buf = buf[2...buf.length]
|
||||
continue # No-op frame
|
||||
elsif buf.count("\xff") == 0
|
||||
@@ -308,7 +340,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
|
||||
bufs << frame['payload']
|
||||
|
||||
if frame['left'] > 0:
|
||||
if frame['left'] > 0
|
||||
buf = buf[-frame['left']...buf.length]
|
||||
else
|
||||
buf = ''
|
||||
@@ -328,10 +360,10 @@ Sec-WebSocket-Accept: %s\r
|
||||
end
|
||||
|
||||
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
|
||||
t[:client].send(buf, 0)
|
||||
t[:client].write(buf)
|
||||
elsif t[:version] == "hixie-76"
|
||||
buf = "\xff\x00"
|
||||
t[:client].send(buf, 0)
|
||||
t[:client].write(buf)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -344,16 +376,21 @@ Sec-WebSocket-Accept: %s\r
|
||||
raise EClose, "ignoring socket not ready"
|
||||
end
|
||||
|
||||
handshake = sock.recv(1024, Socket::MSG_PEEK)
|
||||
#msg "Handshake [#{handshake.inspect}]"
|
||||
handshake = ""
|
||||
msg "About to read from sock [#{sock.inspect}]"
|
||||
handshake = sock.read_nonblock(1024)
|
||||
msg "Handshake [#{handshake.inspect}]"
|
||||
|
||||
if handshake == ""
|
||||
if handshake == nil or handshake == ""
|
||||
raise(EClose, "ignoring empty handshake")
|
||||
else
|
||||
stype = "Plain non-SSL (ws://)"
|
||||
scheme = "ws"
|
||||
if sock.class == OpenSSL::SSL::SSLSocket
|
||||
stype = "SSL (wss://)"
|
||||
scheme = "wss"
|
||||
end
|
||||
retsock = sock
|
||||
sock.recv(1024)
|
||||
end
|
||||
|
||||
h = t[:headers] = {}
|
||||
@@ -365,7 +402,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
|
||||
h[hsplit[1].strip.downcase] = hsplit[2]
|
||||
end
|
||||
#puts "Headers: #{h.inspect}"
|
||||
puts "Headers: #{h.inspect}"
|
||||
|
||||
unless h.has_key?('upgrade') &&
|
||||
h['upgrade'].downcase == 'websocket'
|
||||
@@ -445,7 +482,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
if t[:path] then msg "Path: '%s'" % [t[:path]] end
|
||||
|
||||
#puts "sending reponse #{response.inspect}"
|
||||
retsock.send(response, 0)
|
||||
retsock.write(response)
|
||||
|
||||
# Return the WebSocket socket which may be SSL wrapped
|
||||
return retsock
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# A WebSocket to TCP socket proxy with support for "wss://" encryption.
|
||||
# A WebSocket to TCP socket proxy
|
||||
# Copyright 2011 Joel Martin
|
||||
# Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
@@ -39,7 +39,7 @@ Traffic Legend:
|
||||
end
|
||||
|
||||
# Echo back whatever is received
|
||||
def new_client(client)
|
||||
def new_websocket_client(client)
|
||||
|
||||
msg "connecting to: %s:%s" % [@target_host, @target_port]
|
||||
tsock = TCPSocket.open(@target_host, @target_port)
|
||||
@@ -92,7 +92,7 @@ Traffic Legend:
|
||||
# Receive target data and queue for the client
|
||||
if ins && ins.include?(target)
|
||||
buf = target.recv(@@Buffer_size)
|
||||
if buf.length == 0:
|
||||
if buf.length == 0
|
||||
raise EClose, "Target closed"
|
||||
end
|
||||
|
||||
@@ -128,7 +128,7 @@ parser = OptionParser.new do |o|
|
||||
o.parse!
|
||||
end
|
||||
|
||||
if ARGV.length < 2:
|
||||
if ARGV.length < 2
|
||||
puts "Too few arguments"
|
||||
exit 2
|
||||
end
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
* REBIND_PORT_NEW environment variables are set then bind on the new
|
||||
* port (of localhost) instead of the old port.
|
||||
*
|
||||
* This allows a proxy (such as wsproxy) to run on the old port and translate
|
||||
* traffic to/from the new port.
|
||||
* This allows a bridge/proxy (such as websockify) to run on the old port and
|
||||
* translate traffic to/from the new port.
|
||||
*
|
||||
* Usage:
|
||||
* LD_PRELOAD=./rebind.so \
|
||||
@@ -72,7 +72,7 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
|
||||
if (! do_move) {
|
||||
/* Just pass everything right through to the real bind */
|
||||
ret = (int) func(sockfd, addr, addrlen);
|
||||
ret = (long) func(sockfd, addr, addrlen);
|
||||
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
|
||||
return ret;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
/* Bind to other port on the loopback instead */
|
||||
addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
addr_tmp.sin_port = htons(newport);
|
||||
ret = (int) func(sockfd, &addr_tmp, addrlen_tmp);
|
||||
ret = (long) func(sockfd, &addr_tmp, addrlen_tmp);
|
||||
|
||||
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
|
||||
return ret;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import websockify
|
||||
|
||||
websockify.websocketproxy.websockify_init()
|
||||
@@ -1,6 +1,6 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = '0.1.0'
|
||||
version = '0.6.1'
|
||||
name = 'websockify'
|
||||
long_description = open("README.md").read() + "\n" + \
|
||||
open("CHANGES.txt").read() + "\n"
|
||||
@@ -12,19 +12,27 @@ setup(name=name,
|
||||
classifiers=[
|
||||
"Programming Language :: Python",
|
||||
],
|
||||
data_files=[('share/websockify/include',
|
||||
['include/util.js',
|
||||
'include/base64.js',
|
||||
'include/websock.js']),
|
||||
('share/websockify/include/web-socket-js',
|
||||
['include/web-socket-js/WebSocketMain.swf',
|
||||
'include/web-socket-js/swfobject.js',
|
||||
'include/web-socket-js/web_socket.js'])],
|
||||
keywords='noVNC websockify',
|
||||
license='LGPLv3',
|
||||
url="https://github.com/kanaka/websockify",
|
||||
author="Joel Martin",
|
||||
author_email="github@martintribe.org",
|
||||
|
||||
packages=find_packages(),
|
||||
packages=['websockify'],
|
||||
include_package_data=True,
|
||||
install_requires=['numpy'],
|
||||
zip_safe=False,
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'websockify = websockify:websockify_init',
|
||||
'websockify = websockify.websocketproxy:websockify_init',
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
+12
-11
@@ -10,17 +10,17 @@ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
'''
|
||||
|
||||
import os, sys, select, optparse
|
||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
||||
from websocket import WebSocketServer
|
||||
import os, sys, select, optparse, logging
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
||||
|
||||
class WebSocketEcho(WebSocketServer):
|
||||
class WebSocketEcho(WebSocketRequestHandler):
|
||||
"""
|
||||
WebSockets server that echos back whatever is received from the
|
||||
client. """
|
||||
buffer_size = 8096
|
||||
|
||||
def new_client(self):
|
||||
def new_websocket_client(self):
|
||||
"""
|
||||
Echo back whatever is received.
|
||||
"""
|
||||
@@ -28,28 +28,27 @@ class WebSocketEcho(WebSocketServer):
|
||||
cqueue = []
|
||||
c_pend = 0
|
||||
cpartial = ""
|
||||
rlist = [self.client]
|
||||
rlist = [self.request]
|
||||
|
||||
while True:
|
||||
wlist = []
|
||||
|
||||
if cqueue or c_pend: wlist.append(self.client)
|
||||
if cqueue or c_pend: wlist.append(self.request)
|
||||
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if self.client in outs:
|
||||
if self.request in outs:
|
||||
# Send queued target data to the client
|
||||
c_pend = self.send_frames(cqueue)
|
||||
cqueue = []
|
||||
|
||||
if self.client in ins:
|
||||
if self.request in ins:
|
||||
# Receive client data, decode it, and send it back
|
||||
frames, closed = self.recv_frames()
|
||||
cqueue.extend(frames)
|
||||
|
||||
if closed:
|
||||
self.send_close()
|
||||
raise self.EClose(closed)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
||||
@@ -69,7 +68,9 @@ if __name__ == '__main__':
|
||||
except:
|
||||
parser.error("Invalid arguments")
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
opts.web = "."
|
||||
server = WebSocketEcho(**opts.__dict__)
|
||||
server = WebSocketServer(WebSocketEcho, **opts.__dict__)
|
||||
server.start_server()
|
||||
|
||||
|
||||
+10
-3
@@ -12,7 +12,7 @@ require 'websocket'
|
||||
class WebSocketEcho < WebSocketServer
|
||||
|
||||
# Echo back whatever is received
|
||||
def new_client(client)
|
||||
def new_websocket_client(client)
|
||||
|
||||
cqueue = []
|
||||
c_pend = 0
|
||||
@@ -50,10 +50,17 @@ class WebSocketEcho < WebSocketServer
|
||||
end
|
||||
end
|
||||
|
||||
port = ARGV[0].to_i
|
||||
port = ARGV[0].to_i || 8080
|
||||
puts "Starting server on port #{port}"
|
||||
server_cert = nil
|
||||
server_key = nil
|
||||
if ARGV.length > 2
|
||||
server_cert = ARGV[1]
|
||||
server_key = ARGV[2]
|
||||
end
|
||||
|
||||
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true)
|
||||
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true,
|
||||
'server_cert' => server_cert, 'server_key' => server_key)
|
||||
server.start
|
||||
server.join
|
||||
|
||||
|
||||
+18
-15
@@ -6,35 +6,37 @@ that has a random payload (length and content) that is checksummed and
|
||||
given a sequence number. Any errors are reported and counted.
|
||||
'''
|
||||
|
||||
import sys, os, select, random, time, optparse
|
||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
||||
from websocket import WebSocketServer
|
||||
import sys, os, select, random, time, optparse, logging
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
||||
|
||||
class WebSocketLoad(WebSocketServer):
|
||||
class WebSocketLoadServer(WebSocketServer):
|
||||
|
||||
buffer_size = 65536
|
||||
|
||||
max_packet_size = 10000
|
||||
recv_cnt = 0
|
||||
send_cnt = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.errors = 0
|
||||
self.delay = kwargs.pop('delay')
|
||||
|
||||
WebSocketServer.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class WebSocketLoad(WebSocketRequestHandler):
|
||||
|
||||
max_packet_size = 10000
|
||||
|
||||
def new_websocket_client(self):
|
||||
print "Prepopulating random array"
|
||||
self.rand_array = []
|
||||
for i in range(0, self.max_packet_size):
|
||||
self.rand_array.append(random.randint(0, 9))
|
||||
|
||||
WebSocketServer.__init__(self, *args, **kwargs)
|
||||
|
||||
def new_client(self):
|
||||
self.errors = 0
|
||||
self.send_cnt = 0
|
||||
self.recv_cnt = 0
|
||||
|
||||
try:
|
||||
self.responder(self.client)
|
||||
self.responder(self.request)
|
||||
except:
|
||||
print "accumulated errors:", self.errors
|
||||
self.errors = 0
|
||||
@@ -61,14 +63,13 @@ class WebSocketLoad(WebSocketServer):
|
||||
|
||||
if closed:
|
||||
self.send_close()
|
||||
raise self.EClose(closed)
|
||||
|
||||
now = time.time() * 1000
|
||||
if client in outs:
|
||||
if c_pend:
|
||||
last_send = now
|
||||
c_pend = self.send_frames()
|
||||
elif now > (last_send + self.delay):
|
||||
elif now > (last_send + self.server.delay):
|
||||
last_send = now
|
||||
c_pend = self.send_frames([self.generate()])
|
||||
|
||||
@@ -161,7 +162,9 @@ if __name__ == '__main__':
|
||||
except:
|
||||
parser.error("Invalid arguments")
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
opts.web = "."
|
||||
server = WebSocketLoad(**opts.__dict__)
|
||||
server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__)
|
||||
server.start_server()
|
||||
|
||||
|
||||
@@ -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
|
||||
from select import select
|
||||
|
||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
||||
from websocket import WebSocketServer
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websocket import WebSocketServer
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "val: hixie | hybi_base64 | hybi_binary"
|
||||
print "val: hybi_base64 | hybi_binary"
|
||||
for c in range(0, 256):
|
||||
hixie = WebSocketServer.encode_hixie(chr(c))
|
||||
hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1,
|
||||
base64=True)
|
||||
hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2,
|
||||
base64=False)
|
||||
print "%d: %s | %s | %s" % (c, repr(hixie), repr(hybi_base64),
|
||||
repr(hybi_binary))
|
||||
print "%d: %s | %s" % (c, repr(hybi_base64), repr(hybi_binary))
|
||||
|
||||
|
||||
-930
@@ -1,930 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Python WebSocket library with support for "wss://" encryption.
|
||||
Copyright 2011 Joel Martin
|
||||
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
Supports following protocol versions:
|
||||
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
|
||||
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
||||
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
||||
|
||||
You can make a cert/key with openssl using:
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import os, sys, time, errno, signal, socket, traceback, select
|
||||
import array, struct
|
||||
from cgi import parse_qsl
|
||||
from base64 import b64encode, b64decode
|
||||
|
||||
# Imports that vary by python version
|
||||
|
||||
# python 3.0 differences
|
||||
if sys.hexversion > 0x3000000:
|
||||
b2s = lambda buf: buf.decode('latin_1')
|
||||
s2b = lambda s: s.encode('latin_1')
|
||||
s2a = lambda s: s
|
||||
else:
|
||||
b2s = lambda buf: buf # No-op
|
||||
s2b = lambda s: s # No-op
|
||||
s2a = lambda s: [ord(c) for c in s]
|
||||
try: from io import StringIO
|
||||
except: from cStringIO import StringIO
|
||||
try: from http.server import SimpleHTTPRequestHandler
|
||||
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
try: from urllib.parse import urlsplit
|
||||
except: from urlparse import urlsplit
|
||||
|
||||
# python 2.6 differences
|
||||
try: from hashlib import md5, sha1
|
||||
except: from md5 import md5; from sha import sha as sha1
|
||||
|
||||
# python 2.5 differences
|
||||
try:
|
||||
from struct import pack, unpack_from
|
||||
except:
|
||||
from struct import pack
|
||||
def unpack_from(fmt, buf, offset=0):
|
||||
slice = buffer(buf, offset, struct.calcsize(fmt))
|
||||
return struct.unpack(fmt, slice)
|
||||
|
||||
# Degraded functionality if these imports are missing
|
||||
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
|
||||
('multiprocessing', 'Multi-Processing'),
|
||||
('resource', 'daemonizing')]:
|
||||
try:
|
||||
globals()[mod] = __import__(mod)
|
||||
except ImportError:
|
||||
globals()[mod] = None
|
||||
print("WARNING: no '%s' module, %s is slower or disabled" % (
|
||||
mod, sup))
|
||||
if multiprocessing and sys.platform == 'win32':
|
||||
# make sockets pickle-able/inheritable
|
||||
import multiprocessing.reduction
|
||||
|
||||
|
||||
class WebSocketServer(object):
|
||||
"""
|
||||
WebSockets server class.
|
||||
Must be sub-classed with new_client method definition.
|
||||
"""
|
||||
|
||||
buffer_size = 65536
|
||||
|
||||
server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
||||
Upgrade: WebSocket\r
|
||||
Connection: Upgrade\r
|
||||
%sWebSocket-Origin: %s\r
|
||||
%sWebSocket-Location: %s://%s%s\r
|
||||
"""
|
||||
|
||||
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
|
||||
Upgrade: websocket\r
|
||||
Connection: Upgrade\r
|
||||
Sec-WebSocket-Accept: %s\r
|
||||
"""
|
||||
|
||||
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
|
||||
|
||||
# An exception before the WebSocket connection was established
|
||||
class EClose(Exception):
|
||||
pass
|
||||
|
||||
# An exception while the WebSocket client was connected
|
||||
class CClose(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
|
||||
verbose=False, cert='', key='', ssl_only=None,
|
||||
daemon=False, record='', web='',
|
||||
run_once=False, timeout=0):
|
||||
|
||||
# settings
|
||||
self.verbose = verbose
|
||||
self.listen_host = listen_host
|
||||
self.listen_port = listen_port
|
||||
self.ssl_only = ssl_only
|
||||
self.daemon = daemon
|
||||
self.run_once = run_once
|
||||
self.timeout = timeout
|
||||
|
||||
self.launch_time = time.time()
|
||||
self.ws_connection = False
|
||||
self.handler_id = 1
|
||||
|
||||
# Make paths settings absolute
|
||||
self.cert = os.path.abspath(cert)
|
||||
self.key = self.web = self.record = ''
|
||||
if key:
|
||||
self.key = os.path.abspath(key)
|
||||
if web:
|
||||
self.web = os.path.abspath(web)
|
||||
if record:
|
||||
self.record = os.path.abspath(record)
|
||||
|
||||
if self.web:
|
||||
os.chdir(self.web)
|
||||
|
||||
# Sanity checks
|
||||
if not ssl and self.ssl_only:
|
||||
raise Exception("No 'ssl' module and SSL-only specified")
|
||||
if self.daemon and not resource:
|
||||
raise Exception("Module 'resource' required to daemonize")
|
||||
|
||||
# Show configuration
|
||||
print("WebSocket server settings:")
|
||||
print(" - Listen on %s:%s" % (
|
||||
self.listen_host, self.listen_port))
|
||||
print(" - Flash security policy server")
|
||||
if self.web:
|
||||
print(" - Web server. Web root: %s" % self.web)
|
||||
if ssl:
|
||||
if os.path.exists(self.cert):
|
||||
print(" - SSL/TLS support")
|
||||
if self.ssl_only:
|
||||
print(" - Deny non-SSL/TLS connections")
|
||||
else:
|
||||
print(" - No SSL/TLS support (no cert file)")
|
||||
else:
|
||||
print(" - No SSL/TLS support (no 'ssl' module)")
|
||||
if self.daemon:
|
||||
print(" - Backgrounding (daemon)")
|
||||
if self.record:
|
||||
print(" - Recording to '%s.*'" % self.record)
|
||||
|
||||
#
|
||||
# WebSocketServer static methods
|
||||
#
|
||||
|
||||
@staticmethod
|
||||
def socket(host, port=None, connect=False, prefer_ipv6=False):
|
||||
""" Resolve a host (and optional port) to an IPv4 or IPv6
|
||||
address. Create a socket. Bind to it if listen is set,
|
||||
otherwise connect to it. Return the socket.
|
||||
"""
|
||||
flags = 0
|
||||
if host == '':
|
||||
host = None
|
||||
if connect and not port:
|
||||
raise Exception("Connect mode requires a port")
|
||||
if not connect:
|
||||
flags = flags | socket.AI_PASSIVE
|
||||
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
|
||||
socket.IPPROTO_TCP, flags)
|
||||
if not addrs:
|
||||
raise Exception("Could resolve host '%s'" % host)
|
||||
addrs.sort(key=lambda x: x[0])
|
||||
if prefer_ipv6:
|
||||
addrs.reverse()
|
||||
sock = socket.socket(addrs[0][0], addrs[0][1])
|
||||
if connect:
|
||||
sock.connect(addrs[0][4])
|
||||
else:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind(addrs[0][4])
|
||||
sock.listen(100)
|
||||
return sock
|
||||
|
||||
@staticmethod
|
||||
def daemonize(keepfd=None, chdir='/'):
|
||||
os.umask(0)
|
||||
if chdir:
|
||||
os.chdir(chdir)
|
||||
else:
|
||||
os.chdir('/')
|
||||
os.setgid(os.getgid()) # relinquish elevations
|
||||
os.setuid(os.getuid()) # relinquish elevations
|
||||
|
||||
# Double fork to daemonize
|
||||
if os.fork() > 0: os._exit(0) # Parent exits
|
||||
os.setsid() # Obtain new process group
|
||||
if os.fork() > 0: os._exit(0) # Parent exits
|
||||
|
||||
# Signal handling
|
||||
def terminate(a,b): os._exit(0)
|
||||
signal.signal(signal.SIGTERM, terminate)
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
# Close open files
|
||||
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
||||
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
||||
for fd in reversed(range(maxfd)):
|
||||
try:
|
||||
if fd != keepfd:
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
_, exc, _ = sys.exc_info()
|
||||
if exc.errno != errno.EBADF: raise
|
||||
|
||||
# Redirect I/O to /dev/null
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
|
||||
|
||||
@staticmethod
|
||||
def unmask(buf, f):
|
||||
pstart = f['hlen'] + 4
|
||||
pend = pstart + f['length']
|
||||
if numpy:
|
||||
b = c = s2b('')
|
||||
if f['length'] >= 4:
|
||||
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
||||
offset=f['hlen'], count=1)
|
||||
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
||||
offset=pstart, count=int(f['length'] / 4))
|
||||
#b = numpy.bitwise_xor(data, mask).data
|
||||
b = numpy.bitwise_xor(data, mask).tostring()
|
||||
|
||||
if f['length'] % 4:
|
||||
#print("Partial unmask")
|
||||
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
||||
offset=f['hlen'], count=(f['length'] % 4))
|
||||
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
||||
offset=pend - (f['length'] % 4),
|
||||
count=(f['length'] % 4))
|
||||
c = numpy.bitwise_xor(data, mask).tostring()
|
||||
return b + c
|
||||
else:
|
||||
# Slower fallback
|
||||
data = array.array('B')
|
||||
mask = s2a(f['mask'])
|
||||
data.fromstring(buf[pstart:pend])
|
||||
for i in range(len(data)):
|
||||
data[i] ^= mask[i % 4]
|
||||
return data.tostring()
|
||||
|
||||
@staticmethod
|
||||
def encode_hybi(buf, opcode, base64=False):
|
||||
""" Encode a HyBi style WebSocket frame.
|
||||
Optional opcode:
|
||||
0x0 - continuation
|
||||
0x1 - text frame (base64 encode buf)
|
||||
0x2 - binary frame (use raw buf)
|
||||
0x8 - connection close
|
||||
0x9 - ping
|
||||
0xA - pong
|
||||
"""
|
||||
if base64:
|
||||
buf = b64encode(buf)
|
||||
|
||||
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
|
||||
payload_len = len(buf)
|
||||
if payload_len <= 125:
|
||||
header = pack('>BB', b1, payload_len)
|
||||
elif payload_len > 125 and payload_len < 65536:
|
||||
header = pack('>BBH', b1, 126, payload_len)
|
||||
elif payload_len >= 65536:
|
||||
header = pack('>BBQ', b1, 127, payload_len)
|
||||
|
||||
#print("Encoded: %s" % repr(header + buf))
|
||||
|
||||
return header + buf, len(header), 0
|
||||
|
||||
@staticmethod
|
||||
def decode_hybi(buf, base64=False):
|
||||
""" Decode HyBi style WebSocket packets.
|
||||
Returns:
|
||||
{'fin' : 0_or_1,
|
||||
'opcode' : number,
|
||||
'mask' : 32_bit_number,
|
||||
'hlen' : header_bytes_number,
|
||||
'length' : payload_bytes_number,
|
||||
'payload' : decoded_buffer,
|
||||
'left' : bytes_left_number,
|
||||
'close_code' : number,
|
||||
'close_reason' : string}
|
||||
"""
|
||||
|
||||
f = {'fin' : 0,
|
||||
'opcode' : 0,
|
||||
'mask' : 0,
|
||||
'hlen' : 2,
|
||||
'length' : 0,
|
||||
'payload' : None,
|
||||
'left' : 0,
|
||||
'close_code' : 1000,
|
||||
'close_reason' : ''}
|
||||
|
||||
blen = len(buf)
|
||||
f['left'] = blen
|
||||
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
|
||||
b1, b2 = unpack_from(">BB", buf)
|
||||
f['opcode'] = b1 & 0x0f
|
||||
f['fin'] = (b1 & 0x80) >> 7
|
||||
has_mask = (b2 & 0x80) >> 7
|
||||
|
||||
f['length'] = b2 & 0x7f
|
||||
|
||||
if f['length'] == 126:
|
||||
f['hlen'] = 4
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = unpack_from('>xxH', buf)
|
||||
elif f['length'] == 127:
|
||||
f['hlen'] = 10
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = unpack_from('>xxQ', buf)
|
||||
|
||||
full_len = f['hlen'] + has_mask * 4 + f['length']
|
||||
|
||||
if blen < full_len: # Incomplete frame
|
||||
return f # Incomplete frame header
|
||||
|
||||
# Number of bytes that are part of the next frame(s)
|
||||
f['left'] = blen - full_len
|
||||
|
||||
# Process 1 frame
|
||||
if has_mask:
|
||||
# unmask payload
|
||||
f['mask'] = buf[f['hlen']:f['hlen']+4]
|
||||
f['payload'] = WebSocketServer.unmask(buf, f)
|
||||
else:
|
||||
print("Unmasked frame: %s" % repr(buf))
|
||||
f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
|
||||
|
||||
if base64 and f['opcode'] in [1, 2]:
|
||||
try:
|
||||
f['payload'] = b64decode(f['payload'])
|
||||
except:
|
||||
print("Exception while b64decoding buffer: %s" %
|
||||
repr(buf))
|
||||
raise
|
||||
|
||||
if f['opcode'] == 0x08:
|
||||
if f['length'] >= 2:
|
||||
f['close_code'] = unpack_from(">H", f['payload'])[0]
|
||||
if f['length'] > 3:
|
||||
f['close_reason'] = f['payload'][2:]
|
||||
|
||||
return f
|
||||
|
||||
@staticmethod
|
||||
def encode_hixie(buf):
|
||||
return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
|
||||
|
||||
@staticmethod
|
||||
def decode_hixie(buf):
|
||||
end = buf.find(s2b('\xff'))
|
||||
return {'payload': b64decode(buf[1:end]),
|
||||
'hlen': 1,
|
||||
'length': end - 1,
|
||||
'left': len(buf) - (end + 1)}
|
||||
|
||||
|
||||
@staticmethod
|
||||
def gen_md5(keys):
|
||||
""" Generate hash value for WebSockets hixie-76. """
|
||||
key1 = keys['Sec-WebSocket-Key1']
|
||||
key2 = keys['Sec-WebSocket-Key2']
|
||||
key3 = keys['key3']
|
||||
spaces1 = key1.count(" ")
|
||||
spaces2 = key2.count(" ")
|
||||
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
|
||||
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
|
||||
|
||||
return b2s(md5(pack('>II8s',
|
||||
int(num1), int(num2), key3)).digest())
|
||||
|
||||
#
|
||||
# WebSocketServer logging/output functions
|
||||
#
|
||||
|
||||
def traffic(self, token="."):
|
||||
""" Show traffic flow in verbose mode. """
|
||||
if self.verbose and not self.daemon:
|
||||
sys.stdout.write(token)
|
||||
sys.stdout.flush()
|
||||
|
||||
def msg(self, msg):
|
||||
""" Output message with handler_id prefix. """
|
||||
if not self.daemon:
|
||||
print("% 3d: %s" % (self.handler_id, msg))
|
||||
|
||||
def vmsg(self, msg):
|
||||
""" Same as msg() but only if verbose. """
|
||||
if self.verbose:
|
||||
self.msg(msg)
|
||||
|
||||
#
|
||||
# Main WebSocketServer methods
|
||||
#
|
||||
def send_frames(self, bufs=None):
|
||||
""" Encode and send WebSocket frames. Any frames already
|
||||
queued will be sent first. If buf is not set then only queued
|
||||
frames will be sent. Returns the number of pending frames that
|
||||
could not be fully sent. If returned pending frames is greater
|
||||
than 0, then the caller should call again when the socket is
|
||||
ready. """
|
||||
|
||||
tdelta = int(time.time()*1000) - self.start_time
|
||||
|
||||
if bufs:
|
||||
for buf in bufs:
|
||||
if self.version.startswith("hybi"):
|
||||
if self.base64:
|
||||
encbuf, lenhead, lentail = self.encode_hybi(
|
||||
buf, opcode=1, base64=True)
|
||||
else:
|
||||
encbuf, lenhead, lentail = self.encode_hybi(
|
||||
buf, opcode=2, base64=False)
|
||||
|
||||
else:
|
||||
encbuf, lenhead, lentail = self.encode_hixie(buf)
|
||||
|
||||
if self.rec:
|
||||
self.rec.write("%s,\n" %
|
||||
repr("{%s{" % tdelta
|
||||
+ encbuf[lenhead:-lentail]))
|
||||
|
||||
self.send_parts.append(encbuf)
|
||||
|
||||
while self.send_parts:
|
||||
# Send pending frames
|
||||
buf = self.send_parts.pop(0)
|
||||
sent = self.client.send(buf)
|
||||
|
||||
if sent == len(buf):
|
||||
self.traffic("<")
|
||||
else:
|
||||
self.traffic("<.")
|
||||
self.send_parts.insert(0, buf[sent:])
|
||||
break
|
||||
|
||||
return len(self.send_parts)
|
||||
|
||||
def recv_frames(self):
|
||||
""" Receive and decode WebSocket frames.
|
||||
|
||||
Returns:
|
||||
(bufs_list, closed_string)
|
||||
"""
|
||||
|
||||
closed = False
|
||||
bufs = []
|
||||
tdelta = int(time.time()*1000) - self.start_time
|
||||
|
||||
buf = self.client.recv(self.buffer_size)
|
||||
if len(buf) == 0:
|
||||
closed = {'code': 1000, 'reason': "Client closed abruptly"}
|
||||
return bufs, closed
|
||||
|
||||
if self.recv_part:
|
||||
# Add partially received frames to current read buffer
|
||||
buf = self.recv_part + buf
|
||||
self.recv_part = None
|
||||
|
||||
while buf:
|
||||
if self.version.startswith("hybi"):
|
||||
|
||||
frame = self.decode_hybi(buf, base64=self.base64)
|
||||
#print("Received buf: %s, frame: %s" % (repr(buf), frame))
|
||||
|
||||
if frame['payload'] == None:
|
||||
# Incomplete/partial frame
|
||||
self.traffic("}.")
|
||||
if frame['left'] > 0:
|
||||
self.recv_part = buf[-frame['left']:]
|
||||
break
|
||||
else:
|
||||
if frame['opcode'] == 0x8: # connection close
|
||||
closed = {'code': frame['close_code'],
|
||||
'reason': frame['close_reason']}
|
||||
break
|
||||
|
||||
else:
|
||||
if buf[0:2] == s2b('\xff\x00'):
|
||||
closed = {'code': 1000,
|
||||
'reason': "Client sent orderly close frame"}
|
||||
break
|
||||
|
||||
elif buf[0:2] == s2b('\x00\xff'):
|
||||
buf = buf[2:]
|
||||
continue # No-op
|
||||
|
||||
elif buf.count(s2b('\xff')) == 0:
|
||||
# Partial frame
|
||||
self.traffic("}.")
|
||||
self.recv_part = buf
|
||||
break
|
||||
|
||||
frame = self.decode_hixie(buf)
|
||||
|
||||
self.traffic("}")
|
||||
|
||||
if self.rec:
|
||||
start = frame['hlen']
|
||||
end = frame['hlen'] + frame['length']
|
||||
self.rec.write("%s,\n" %
|
||||
repr("}%s}" % tdelta + buf[start:end]))
|
||||
|
||||
|
||||
bufs.append(frame['payload'])
|
||||
|
||||
if frame['left']:
|
||||
buf = buf[-frame['left']:]
|
||||
else:
|
||||
buf = ''
|
||||
|
||||
return bufs, closed
|
||||
|
||||
def send_close(self, code=1000, reason=''):
|
||||
""" Send a WebSocket orderly close frame. """
|
||||
|
||||
if self.version.startswith("hybi"):
|
||||
msg = pack(">H%ds" % len(reason), code, reason)
|
||||
|
||||
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
||||
self.client.send(buf)
|
||||
|
||||
elif self.version == "hixie-76":
|
||||
buf = s2b('\xff\x00')
|
||||
self.client.send(buf)
|
||||
|
||||
# No orderly close for 75
|
||||
|
||||
def do_handshake(self, sock, address):
|
||||
"""
|
||||
do_handshake does the following:
|
||||
- Peek at the first few bytes from the socket.
|
||||
- If the connection is Flash policy request then answer it,
|
||||
close the socket and return.
|
||||
- If the connection is an HTTPS/SSL/TLS connection then SSL
|
||||
wrap the socket.
|
||||
- Read from the (possibly wrapped) socket.
|
||||
- If we have received a HTTP GET request and the webserver
|
||||
functionality is enabled, answer it, close the socket and
|
||||
return.
|
||||
- Assume we have a WebSockets connection, parse the client
|
||||
handshake data.
|
||||
- Send a WebSockets handshake server response.
|
||||
- Return the socket for this WebSocket client.
|
||||
"""
|
||||
|
||||
stype = ""
|
||||
|
||||
ready = select.select([sock], [], [], 3)[0]
|
||||
if not ready:
|
||||
raise self.EClose("ignoring socket not ready")
|
||||
# Peek, but do not read the data so that we have a opportunity
|
||||
# to SSL wrap the socket first
|
||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
||||
#self.msg("Handshake [%s]" % handshake)
|
||||
|
||||
if handshake == "":
|
||||
raise self.EClose("ignoring empty handshake")
|
||||
|
||||
elif handshake.startswith(s2b("<policy-file-request/>")):
|
||||
# Answer Flash policy request
|
||||
handshake = sock.recv(1024)
|
||||
sock.send(s2b(self.policy_response))
|
||||
raise self.EClose("Sending flash policy response")
|
||||
|
||||
elif handshake[0] in ("\x16", "\x80", 22, 128):
|
||||
# SSL wrap the connection
|
||||
if not ssl:
|
||||
raise self.EClose("SSL connection but no 'ssl' module")
|
||||
if not os.path.exists(self.cert):
|
||||
raise self.EClose("SSL connection but '%s' not found"
|
||||
% self.cert)
|
||||
retsock = None
|
||||
try:
|
||||
retsock = ssl.wrap_socket(
|
||||
sock,
|
||||
server_side=True,
|
||||
certfile=self.cert,
|
||||
keyfile=self.key)
|
||||
except ssl.SSLError:
|
||||
_, x, _ = sys.exc_info()
|
||||
if x.args[0] == ssl.SSL_ERROR_EOF:
|
||||
if len(x.args) > 1:
|
||||
raise self.EClose(x.args[1])
|
||||
else:
|
||||
raise self.EClose("Got SSL_ERROR_EOF")
|
||||
else:
|
||||
raise
|
||||
|
||||
scheme = "wss"
|
||||
stype = "SSL/TLS (wss://)"
|
||||
|
||||
elif self.ssl_only:
|
||||
raise self.EClose("non-SSL connection received but disallowed")
|
||||
|
||||
else:
|
||||
retsock = sock
|
||||
scheme = "ws"
|
||||
stype = "Plain non-SSL (ws://)"
|
||||
|
||||
wsh = WSRequestHandler(retsock, address, not self.web)
|
||||
if wsh.last_code == 101:
|
||||
# Continue on to handle WebSocket upgrade
|
||||
pass
|
||||
elif wsh.last_code == 405:
|
||||
raise self.EClose("Normal web request received but disallowed")
|
||||
elif wsh.last_code < 200 or wsh.last_code >= 300:
|
||||
raise self.EClose(wsh.last_message)
|
||||
elif self.verbose:
|
||||
raise self.EClose(wsh.last_message)
|
||||
else:
|
||||
raise self.EClose("")
|
||||
|
||||
h = self.headers = wsh.headers
|
||||
path = self.path = wsh.path
|
||||
|
||||
prot = 'WebSocket-Protocol'
|
||||
protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
|
||||
|
||||
ver = h.get('Sec-WebSocket-Version')
|
||||
if ver:
|
||||
# HyBi/IETF version of the protocol
|
||||
|
||||
# HyBi-07 report version 7
|
||||
# HyBi-08 - HyBi-12 report version 8
|
||||
# HyBi-13 reports version 13
|
||||
if ver in ['7', '8', '13']:
|
||||
self.version = "hybi-%02d" % int(ver)
|
||||
else:
|
||||
raise self.EClose('Unsupported protocol version %s' % ver)
|
||||
|
||||
key = h['Sec-WebSocket-Key']
|
||||
|
||||
# Choose binary if client supports it
|
||||
if 'binary' in protocols:
|
||||
self.base64 = False
|
||||
elif 'base64' in protocols:
|
||||
self.base64 = True
|
||||
else:
|
||||
raise self.EClose("Client must support 'binary' or 'base64' protocol")
|
||||
|
||||
# Generate the hash value for the accept header
|
||||
accept = b64encode(sha1(s2b(key + self.GUID)).digest())
|
||||
|
||||
response = self.server_handshake_hybi % b2s(accept)
|
||||
if self.base64:
|
||||
response += "Sec-WebSocket-Protocol: base64\r\n"
|
||||
else:
|
||||
response += "Sec-WebSocket-Protocol: binary\r\n"
|
||||
response += "\r\n"
|
||||
|
||||
else:
|
||||
# Hixie version of the protocol (75 or 76)
|
||||
|
||||
if h.get('key3'):
|
||||
trailer = self.gen_md5(h)
|
||||
pre = "Sec-"
|
||||
self.version = "hixie-76"
|
||||
else:
|
||||
trailer = ""
|
||||
pre = ""
|
||||
self.version = "hixie-75"
|
||||
|
||||
# We only support base64 in Hixie era
|
||||
self.base64 = True
|
||||
|
||||
response = self.server_handshake_hixie % (pre,
|
||||
h['Origin'], pre, scheme, h['Host'], path)
|
||||
|
||||
if 'base64' in protocols:
|
||||
response += "%sWebSocket-Protocol: base64\r\n" % pre
|
||||
else:
|
||||
self.msg("Warning: client does not report 'base64' protocol support")
|
||||
response += "\r\n" + trailer
|
||||
|
||||
self.msg("%s: %s WebSocket connection" % (address[0], stype))
|
||||
self.msg("%s: Version %s, base64: '%s'" % (address[0],
|
||||
self.version, self.base64))
|
||||
if self.path != '/':
|
||||
self.msg("%s: Path: '%s'" % (address[0], self.path))
|
||||
|
||||
|
||||
# Send server WebSockets handshake response
|
||||
#self.msg("sending response [%s]" % response)
|
||||
retsock.send(s2b(response))
|
||||
|
||||
# Return the WebSockets socket which may be SSL wrapped
|
||||
return retsock
|
||||
|
||||
|
||||
#
|
||||
# Events that can/should be overridden in sub-classes
|
||||
#
|
||||
def started(self):
|
||||
""" Called after WebSockets startup """
|
||||
self.vmsg("WebSockets server started")
|
||||
|
||||
def poll(self):
|
||||
""" Run periodically while waiting for connections. """
|
||||
#self.vmsg("Running poll()")
|
||||
pass
|
||||
|
||||
def fallback_SIGCHLD(self, sig, stack):
|
||||
# Reap zombies when using os.fork() (python 2.4)
|
||||
self.vmsg("Got SIGCHLD, reaping zombies")
|
||||
try:
|
||||
result = os.waitpid(-1, os.WNOHANG)
|
||||
while result[0]:
|
||||
self.vmsg("Reaped child process %s" % result[0])
|
||||
result = os.waitpid(-1, os.WNOHANG)
|
||||
except (OSError):
|
||||
pass
|
||||
|
||||
def do_SIGINT(self, sig, stack):
|
||||
self.msg("Got SIGINT, exiting")
|
||||
sys.exit(0)
|
||||
|
||||
def top_new_client(self, startsock, address):
|
||||
""" Do something with a WebSockets client connection. """
|
||||
# Initialize per client settings
|
||||
self.send_parts = []
|
||||
self.recv_part = None
|
||||
self.base64 = False
|
||||
self.rec = None
|
||||
self.start_time = int(time.time()*1000)
|
||||
|
||||
# handler process
|
||||
try:
|
||||
try:
|
||||
self.client = self.do_handshake(startsock, address)
|
||||
|
||||
if self.record:
|
||||
# Record raw frame data as JavaScript array
|
||||
fname = "%s.%s" % (self.record,
|
||||
self.handler_id)
|
||||
self.msg("opening record file: %s" % fname)
|
||||
self.rec = open(fname, 'w+')
|
||||
self.rec.write("var VNC_frame_data = [\n")
|
||||
|
||||
self.ws_connection = True
|
||||
self.new_client()
|
||||
except self.CClose:
|
||||
# Close the client
|
||||
_, exc, _ = sys.exc_info()
|
||||
if self.client:
|
||||
self.send_close(exc.args[0], exc.args[1])
|
||||
except self.EClose:
|
||||
_, exc, _ = sys.exc_info()
|
||||
# Connection was not a WebSockets connection
|
||||
if exc.args[0]:
|
||||
self.msg("%s: %s" % (address[0], exc.args[0]))
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
self.msg("handler exception: %s" % str(exc))
|
||||
if self.verbose:
|
||||
self.msg(traceback.format_exc())
|
||||
finally:
|
||||
if self.rec:
|
||||
self.rec.write("'EOF']\n")
|
||||
self.rec.close()
|
||||
|
||||
if self.client and self.client != startsock:
|
||||
# Close the SSL wrapped socket
|
||||
# Original socket closed by caller
|
||||
self.client.close()
|
||||
|
||||
def new_client(self):
|
||||
""" Do something with a WebSockets client connection. """
|
||||
raise("WebSocketServer.new_client() must be overloaded")
|
||||
|
||||
def start_server(self):
|
||||
"""
|
||||
Daemonize if requested. Listen for for connections. Run
|
||||
do_handshake() method for each connection. If the connection
|
||||
is a WebSockets client then call new_client() method (which must
|
||||
be overridden) for each new client connection.
|
||||
"""
|
||||
lsock = self.socket(self.listen_host, self.listen_port)
|
||||
|
||||
if self.daemon:
|
||||
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
|
||||
|
||||
self.started() # Some things need to happen after daemonizing
|
||||
|
||||
# Allow override of SIGINT
|
||||
signal.signal(signal.SIGINT, self.do_SIGINT)
|
||||
if not multiprocessing:
|
||||
# os.fork() (python 2.4) child reaper
|
||||
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
|
||||
|
||||
while True:
|
||||
try:
|
||||
try:
|
||||
self.client = None
|
||||
startsock = None
|
||||
pid = err = 0
|
||||
|
||||
time_elapsed = time.time() - self.launch_time
|
||||
if self.timeout and time_elapsed > self.timeout:
|
||||
self.msg('listener exit due to --timeout %s'
|
||||
% self.timeout)
|
||||
break
|
||||
|
||||
try:
|
||||
self.poll()
|
||||
|
||||
ready = select.select([lsock], [], [], 1)[0]
|
||||
if lsock in ready:
|
||||
startsock, address = lsock.accept()
|
||||
else:
|
||||
continue
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
if hasattr(exc, 'errno'):
|
||||
err = exc.errno
|
||||
elif hasattr(exc, 'args'):
|
||||
err = exc.args[0]
|
||||
else:
|
||||
err = exc[0]
|
||||
if err == errno.EINTR:
|
||||
self.vmsg("Ignoring interrupted syscall")
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
if self.run_once:
|
||||
# Run in same process if run_once
|
||||
self.top_new_client(startsock, address)
|
||||
if self.ws_connection :
|
||||
self.msg('%s: exiting due to --run-once'
|
||||
% address[0])
|
||||
break
|
||||
elif multiprocessing:
|
||||
self.vmsg('%s: new handler Process' % address[0])
|
||||
p = multiprocessing.Process(
|
||||
target=self.top_new_client,
|
||||
args=(startsock, address))
|
||||
p.start()
|
||||
# child will not return
|
||||
else:
|
||||
# python 2.4
|
||||
self.vmsg('%s: forking handler' % address[0])
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
# child handler process
|
||||
self.top_new_client(startsock, address)
|
||||
break # child process exits
|
||||
|
||||
# parent process
|
||||
self.handler_id += 1
|
||||
|
||||
except KeyboardInterrupt:
|
||||
_, exc, _ = sys.exc_info()
|
||||
print("In KeyboardInterrupt")
|
||||
pass
|
||||
except SystemExit:
|
||||
_, exc, _ = sys.exc_info()
|
||||
print("In SystemExit")
|
||||
break
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
self.msg("handler exception: %s" % str(exc))
|
||||
if self.verbose:
|
||||
self.msg(traceback.format_exc())
|
||||
|
||||
finally:
|
||||
if startsock:
|
||||
startsock.close()
|
||||
|
||||
|
||||
# HTTP handler with WebSocket upgrade support
|
||||
class WSRequestHandler(SimpleHTTPRequestHandler):
|
||||
def __init__(self, req, addr, only_upgrade=False):
|
||||
self.only_upgrade = only_upgrade # only allow upgrades
|
||||
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
|
||||
|
||||
def do_GET(self):
|
||||
if (self.headers.get('upgrade') and
|
||||
self.headers.get('upgrade').lower() == 'websocket'):
|
||||
|
||||
if (self.headers.get('sec-websocket-key1') or
|
||||
self.headers.get('websocket-key1')):
|
||||
# For Hixie-76 read out the key hash
|
||||
self.headers.__setitem__('key3', self.rfile.read(8))
|
||||
|
||||
# Just indicate that an WebSocket upgrade is needed
|
||||
self.last_code = 101
|
||||
self.last_message = "101 Switching Protocols"
|
||||
elif self.only_upgrade:
|
||||
# Normal web request responses are disabled
|
||||
self.last_code = 405
|
||||
self.last_message = "405 Method Not Allowed"
|
||||
else:
|
||||
SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
def send_response(self, code, message=None):
|
||||
# Save the status code
|
||||
self.last_code = code
|
||||
SimpleHTTPRequestHandler.send_response(self, code, message)
|
||||
|
||||
def log_message(self, f, *args):
|
||||
# Save instead of printing
|
||||
self.last_message = f % args
|
||||
|
||||
-285
@@ -1,285 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
A WebSocket to TCP socket proxy with support for "wss://" encryption.
|
||||
Copyright 2011 Joel Martin
|
||||
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
You can make a cert/key with openssl using:
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import socket, optparse, time, os, sys, subprocess
|
||||
from select import select
|
||||
from websocket import WebSocketServer
|
||||
|
||||
class WebSocketProxy(WebSocketServer):
|
||||
"""
|
||||
Proxy traffic to and from a WebSockets client to a normal TCP
|
||||
socket server target. All traffic to/from the client is base64
|
||||
encoded/decoded to allow binary data to be sent/received to/from
|
||||
the target.
|
||||
"""
|
||||
|
||||
buffer_size = 65536
|
||||
|
||||
traffic_legend = """
|
||||
Traffic Legend:
|
||||
} - Client receive
|
||||
}. - Client receive partial
|
||||
{ - Target receive
|
||||
|
||||
> - Target send
|
||||
>. - Target send partial
|
||||
< - Client send
|
||||
<. - Client send partial
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Save off proxy specific options
|
||||
self.target_host = kwargs.pop('target_host')
|
||||
self.target_port = kwargs.pop('target_port')
|
||||
self.wrap_cmd = kwargs.pop('wrap_cmd')
|
||||
self.wrap_mode = kwargs.pop('wrap_mode')
|
||||
# Last 3 timestamps command was run
|
||||
self.wrap_times = [0, 0, 0]
|
||||
|
||||
if self.wrap_cmd:
|
||||
rebinder_path = ['./', os.path.dirname(sys.argv[0])]
|
||||
self.rebinder = None
|
||||
|
||||
for rdir in rebinder_path:
|
||||
rpath = os.path.join(rdir, "rebind.so")
|
||||
if os.path.exists(rpath):
|
||||
self.rebinder = rpath
|
||||
break
|
||||
|
||||
if not self.rebinder:
|
||||
raise Exception("rebind.so not found, perhaps you need to run make")
|
||||
|
||||
self.target_host = "127.0.0.1" # Loopback
|
||||
# Find a free high port
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.bind(('', 0))
|
||||
self.target_port = sock.getsockname()[1]
|
||||
sock.close()
|
||||
|
||||
os.environ.update({
|
||||
"LD_PRELOAD": self.rebinder,
|
||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||
"REBIND_NEW_PORT": str(self.target_port)})
|
||||
|
||||
WebSocketServer.__init__(self, *args, **kwargs)
|
||||
|
||||
def run_wrap_cmd(self):
|
||||
print("Starting '%s'" % " ".join(self.wrap_cmd))
|
||||
self.wrap_times.append(time.time())
|
||||
self.wrap_times.pop(0)
|
||||
self.cmd = subprocess.Popen(
|
||||
self.wrap_cmd, env=os.environ)
|
||||
self.spawn_message = True
|
||||
|
||||
def started(self):
|
||||
"""
|
||||
Called after Websockets server startup (i.e. after daemonize)
|
||||
"""
|
||||
# Need to call wrapped command after daemonization so we can
|
||||
# know when the wrapped command exits
|
||||
if self.wrap_cmd:
|
||||
print(" - proxying from %s:%s to '%s' (port %s)\n" % (
|
||||
self.listen_host, self.listen_port,
|
||||
" ".join(self.wrap_cmd), self.target_port))
|
||||
self.run_wrap_cmd()
|
||||
else:
|
||||
print(" - proxying from %s:%s to %s:%s\n" % (
|
||||
self.listen_host, self.listen_port,
|
||||
self.target_host, self.target_port))
|
||||
|
||||
def poll(self):
|
||||
# If we are wrapping a command, check it's status
|
||||
|
||||
if self.wrap_cmd and self.cmd:
|
||||
ret = self.cmd.poll()
|
||||
if ret != None:
|
||||
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
|
||||
self.cmd = None
|
||||
|
||||
if self.wrap_cmd and self.cmd == None:
|
||||
# Response to wrapped command being gone
|
||||
if self.wrap_mode == "ignore":
|
||||
pass
|
||||
elif self.wrap_mode == "exit":
|
||||
sys.exit(ret)
|
||||
elif self.wrap_mode == "respawn":
|
||||
now = time.time()
|
||||
avg = sum(self.wrap_times)/len(self.wrap_times)
|
||||
if (now - avg) < 10:
|
||||
# 3 times in the last 10 seconds
|
||||
if self.spawn_message:
|
||||
print("Command respawning too fast")
|
||||
self.spawn_message = False
|
||||
else:
|
||||
self.run_wrap_cmd()
|
||||
|
||||
#
|
||||
# Routines above this point are run in the master listener
|
||||
# process.
|
||||
#
|
||||
|
||||
#
|
||||
# Routines below this point are connection handler routines and
|
||||
# will be run in a separate forked process for each connection.
|
||||
#
|
||||
|
||||
def new_client(self):
|
||||
"""
|
||||
Called after a new WebSocket connection has been established.
|
||||
"""
|
||||
|
||||
# Connect to the target
|
||||
self.msg("connecting to: %s:%s" % (
|
||||
self.target_host, self.target_port))
|
||||
tsock = self.socket(self.target_host, self.target_port,
|
||||
connect=True)
|
||||
|
||||
if self.verbose and not self.daemon:
|
||||
print(self.traffic_legend)
|
||||
|
||||
# Start proxying
|
||||
try:
|
||||
self.do_proxy(tsock)
|
||||
except:
|
||||
if tsock:
|
||||
tsock.shutdown(socket.SHUT_RDWR)
|
||||
tsock.close()
|
||||
self.vmsg("%s:%s: Target closed" %(
|
||||
self.target_host, self.target_port))
|
||||
raise
|
||||
|
||||
def do_proxy(self, target):
|
||||
"""
|
||||
Proxy client WebSocket to normal target socket.
|
||||
"""
|
||||
cqueue = []
|
||||
c_pend = 0
|
||||
tqueue = []
|
||||
rlist = [self.client, target]
|
||||
|
||||
while True:
|
||||
wlist = []
|
||||
|
||||
if tqueue: wlist.append(target)
|
||||
if cqueue or c_pend: wlist.append(self.client)
|
||||
ins, outs, excepts = select(rlist, wlist, [], 1)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if target in outs:
|
||||
# Send queued client data to the target
|
||||
dat = tqueue.pop(0)
|
||||
sent = target.send(dat)
|
||||
if sent == len(dat):
|
||||
self.traffic(">")
|
||||
else:
|
||||
# requeue the remaining data
|
||||
tqueue.insert(0, dat[sent:])
|
||||
self.traffic(".>")
|
||||
|
||||
|
||||
if target in ins:
|
||||
# Receive target data, encode it and queue for client
|
||||
buf = target.recv(self.buffer_size)
|
||||
if len(buf) == 0:
|
||||
raise self.CClose(1000, "Target closed")
|
||||
|
||||
cqueue.append(buf)
|
||||
self.traffic("{")
|
||||
|
||||
|
||||
if self.client in outs:
|
||||
# Send queued target data to the client
|
||||
c_pend = self.send_frames(cqueue)
|
||||
|
||||
cqueue = []
|
||||
|
||||
|
||||
if self.client in ins:
|
||||
# Receive client data, decode it, and queue for target
|
||||
bufs, closed = self.recv_frames()
|
||||
tqueue.extend(bufs)
|
||||
|
||||
if closed:
|
||||
# TODO: What about blocking on client socket?
|
||||
raise self.CClose(closed['code'], closed['reason'])
|
||||
|
||||
def websockify_init():
|
||||
usage = "\n %prog [options]"
|
||||
usage += " [source_addr:]source_port target_addr:target_port"
|
||||
usage += "\n %prog [options]"
|
||||
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
||||
parser = optparse.OptionParser(usage=usage)
|
||||
parser.add_option("--verbose", "-v", action="store_true",
|
||||
help="verbose messages and per frame traffic")
|
||||
parser.add_option("--record",
|
||||
help="record sessions to FILE.[session_number]", metavar="FILE")
|
||||
parser.add_option("--daemon", "-D",
|
||||
dest="daemon", action="store_true",
|
||||
help="become a daemon (background process)")
|
||||
parser.add_option("--run-once", action="store_true",
|
||||
help="handle a single WebSocket connection and exit")
|
||||
parser.add_option("--timeout", type=int, default=0,
|
||||
help="after TIMEOUT seconds exit when not connected")
|
||||
parser.add_option("--cert", default="self.pem",
|
||||
help="SSL certificate file")
|
||||
parser.add_option("--key", default=None,
|
||||
help="SSL key file (if separate from cert)")
|
||||
parser.add_option("--ssl-only", action="store_true",
|
||||
help="disallow non-encrypted connections")
|
||||
parser.add_option("--web", default=None, metavar="DIR",
|
||||
help="run webserver on same port. Serve files from DIR.")
|
||||
parser.add_option("--wrap-mode", default="exit", metavar="MODE",
|
||||
choices=["exit", "ignore", "respawn"],
|
||||
help="action to take when the wrapped program exits "
|
||||
"or daemonizes: exit (default), ignore, respawn")
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
# Sanity checks
|
||||
if len(args) < 2:
|
||||
parser.error("Too few arguments")
|
||||
if sys.argv.count('--'):
|
||||
opts.wrap_cmd = args[1:]
|
||||
else:
|
||||
opts.wrap_cmd = None
|
||||
if len(args) > 2:
|
||||
parser.error("Too many arguments")
|
||||
|
||||
if opts.ssl_only and not os.path.exists(opts.cert):
|
||||
parser.error("SSL only and %s not found" % opts.cert)
|
||||
|
||||
# Parse host:port and convert ports to numbers
|
||||
if args[0].count(':') > 0:
|
||||
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
|
||||
else:
|
||||
opts.listen_host, opts.listen_port = '', args[0]
|
||||
|
||||
try: opts.listen_port = int(opts.listen_port)
|
||||
except: parser.error("Error parsing listen port")
|
||||
|
||||
if opts.wrap_cmd:
|
||||
opts.target_host = None
|
||||
opts.target_port = None
|
||||
else:
|
||||
if args[1].count(':') > 0:
|
||||
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
|
||||
else:
|
||||
parser.error("Error parsing target")
|
||||
try: opts.target_port = int(opts.target_port)
|
||||
except: parser.error("Error parsing target port")
|
||||
|
||||
# Create and start the WebSockets proxy
|
||||
server = WebSocketProxy(**opts.__dict__)
|
||||
server.start_server()
|
||||
|
||||
if __name__ == '__main__':
|
||||
websockify_init()
|
||||
+1
-1
@@ -1 +1 @@
|
||||
websockify
|
||||
run
|
||||
@@ -0,0 +1,2 @@
|
||||
from websocket import *
|
||||
from websocketproxy import *
|
||||
File diff suppressed because it is too large
Load Diff
Executable
+471
@@ -0,0 +1,471 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
A WebSocket to TCP socket proxy with support for "wss://" encryption.
|
||||
Copyright 2011 Joel Martin
|
||||
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
You can make a cert/key with openssl using:
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import signal, socket, optparse, time, os, sys, subprocess, logging
|
||||
try: from socketserver import ForkingMixIn
|
||||
except: from SocketServer import ForkingMixIn
|
||||
try: from http.server import HTTPServer
|
||||
except: from BaseHTTPServer import HTTPServer
|
||||
from select import select
|
||||
import websocket
|
||||
try:
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
except:
|
||||
from cgi import parse_qs
|
||||
from urlparse import urlparse
|
||||
|
||||
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
|
||||
|
||||
traffic_legend = """
|
||||
Traffic Legend:
|
||||
} - Client receive
|
||||
}. - Client receive partial
|
||||
{ - Target receive
|
||||
|
||||
> - Target send
|
||||
>. - Target send partial
|
||||
< - Client send
|
||||
<. - Client send partial
|
||||
"""
|
||||
|
||||
def new_websocket_client(self):
|
||||
"""
|
||||
Called after a new WebSocket connection has been established.
|
||||
"""
|
||||
# Checks if we receive a token, and look
|
||||
# for a valid target for it then
|
||||
if self.server.target_cfg:
|
||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.target_cfg, self.path)
|
||||
|
||||
# Connect to the target
|
||||
if self.server.wrap_cmd:
|
||||
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port)
|
||||
elif self.server.unix_target:
|
||||
msg = "connecting to unix socket: %s" % self.server.unix_target
|
||||
else:
|
||||
msg = "connecting to: %s:%s" % (
|
||||
self.server.target_host, self.server.target_port)
|
||||
|
||||
if self.server.ssl_target:
|
||||
msg += " (using SSL)"
|
||||
self.log_message(msg)
|
||||
|
||||
tsock = websocket.WebSocketServer.socket(self.server.target_host,
|
||||
self.server.target_port,
|
||||
connect=True, use_ssl=self.server.ssl_target, unix_socket=self.server.unix_target)
|
||||
|
||||
self.print_traffic(self.traffic_legend)
|
||||
|
||||
# Start proxying
|
||||
try:
|
||||
self.do_proxy(tsock)
|
||||
except:
|
||||
if tsock:
|
||||
tsock.shutdown(socket.SHUT_RDWR)
|
||||
tsock.close()
|
||||
if self.verbose:
|
||||
self.log_message("%s:%s: Closed target" %(
|
||||
self.server.target_host, self.server.target_port))
|
||||
raise
|
||||
|
||||
def get_target(self, target_cfg, path):
|
||||
"""
|
||||
Parses the path, extracts a token, and looks for a valid
|
||||
target for that token in the configuration file(s). Sets
|
||||
target_host and target_port if successful
|
||||
"""
|
||||
# The files in targets contain the lines
|
||||
# in the form of token: host:port
|
||||
|
||||
# Extract the token parameter from url
|
||||
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
||||
|
||||
if not args.has_key('token') or not len(args['token']):
|
||||
raise self.EClose("Token not present")
|
||||
|
||||
token = args['token'][0].rstrip('\n')
|
||||
|
||||
# target_cfg can be a single config file or directory of
|
||||
# config files
|
||||
if os.path.isdir(target_cfg):
|
||||
cfg_files = [os.path.join(target_cfg, f)
|
||||
for f in os.listdir(target_cfg)]
|
||||
else:
|
||||
cfg_files = [target_cfg]
|
||||
|
||||
targets = {}
|
||||
for f in cfg_files:
|
||||
for line in [l.strip() for l in file(f).readlines()]:
|
||||
if line and not line.startswith('#'):
|
||||
ttoken, target = line.split(': ')
|
||||
targets[ttoken] = target.strip()
|
||||
|
||||
self.vmsg("Target config: %s" % repr(targets))
|
||||
|
||||
if targets.has_key(token):
|
||||
return targets[token].split(':')
|
||||
else:
|
||||
raise self.EClose("Token '%s' not found" % token)
|
||||
|
||||
def do_proxy(self, target):
|
||||
"""
|
||||
Proxy client WebSocket to normal target socket.
|
||||
"""
|
||||
cqueue = []
|
||||
c_pend = 0
|
||||
tqueue = []
|
||||
rlist = [self.request, target]
|
||||
|
||||
while True:
|
||||
wlist = []
|
||||
|
||||
if tqueue: wlist.append(target)
|
||||
if cqueue or c_pend: wlist.append(self.request)
|
||||
ins, outs, excepts = select(rlist, wlist, [], 1)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if self.request in outs:
|
||||
# Send queued target data to the client
|
||||
c_pend = self.send_frames(cqueue)
|
||||
|
||||
cqueue = []
|
||||
|
||||
if self.request in ins:
|
||||
# Receive client data, decode it, and queue for target
|
||||
bufs, closed = self.recv_frames()
|
||||
tqueue.extend(bufs)
|
||||
|
||||
if closed:
|
||||
# TODO: What about blocking on client socket?
|
||||
if self.verbose:
|
||||
self.log_message("%s:%s: Client closed connection" %(
|
||||
self.server.target_host, self.server.target_port))
|
||||
raise self.CClose(closed['code'], closed['reason'])
|
||||
|
||||
|
||||
if target in outs:
|
||||
# Send queued client data to the target
|
||||
dat = tqueue.pop(0)
|
||||
sent = target.send(dat)
|
||||
if sent == len(dat):
|
||||
self.print_traffic(">")
|
||||
else:
|
||||
# requeue the remaining data
|
||||
tqueue.insert(0, dat[sent:])
|
||||
self.print_traffic(".>")
|
||||
|
||||
|
||||
if target in ins:
|
||||
# Receive target data, encode it and queue for client
|
||||
buf = target.recv(self.buffer_size)
|
||||
if len(buf) == 0:
|
||||
if self.verbose:
|
||||
self.log_message("%s:%s: Target closed connection" %(
|
||||
self.server.target_host, self.server.target_port))
|
||||
raise self.CClose(1000, "Target closed")
|
||||
|
||||
cqueue.append(buf)
|
||||
self.print_traffic("{")
|
||||
|
||||
class WebSocketProxy(websocket.WebSocketServer):
|
||||
"""
|
||||
Proxy traffic to and from a WebSockets client to a normal TCP
|
||||
socket server target. All traffic to/from the client is base64
|
||||
encoded/decoded to allow binary data to be sent/received to/from
|
||||
the target.
|
||||
"""
|
||||
|
||||
buffer_size = 65536
|
||||
|
||||
def __init__(self, RequestHandlerClass=ProxyRequestHandler, *args, **kwargs):
|
||||
# Save off proxy specific options
|
||||
self.target_host = kwargs.pop('target_host', None)
|
||||
self.target_port = kwargs.pop('target_port', None)
|
||||
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
|
||||
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||
self.unix_target = kwargs.pop('unix_target', None)
|
||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||
self.target_cfg = kwargs.pop('target_cfg', None)
|
||||
# Last 3 timestamps command was run
|
||||
self.wrap_times = [0, 0, 0]
|
||||
|
||||
if self.wrap_cmd:
|
||||
wsdir = os.path.dirname(sys.argv[0])
|
||||
rebinder_path = [os.path.join(wsdir, "..", "lib"),
|
||||
os.path.join(wsdir, "..", "lib", "websockify"),
|
||||
wsdir]
|
||||
self.rebinder = None
|
||||
|
||||
for rdir in rebinder_path:
|
||||
rpath = os.path.join(rdir, "rebind.so")
|
||||
if os.path.exists(rpath):
|
||||
self.rebinder = rpath
|
||||
break
|
||||
|
||||
if not self.rebinder:
|
||||
raise Exception("rebind.so not found, perhaps you need to run make")
|
||||
self.rebinder = os.path.abspath(self.rebinder)
|
||||
|
||||
self.target_host = "127.0.0.1" # Loopback
|
||||
# Find a free high port
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.bind(('', 0))
|
||||
self.target_port = sock.getsockname()[1]
|
||||
sock.close()
|
||||
|
||||
os.environ.update({
|
||||
"LD_PRELOAD": self.rebinder,
|
||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||
"REBIND_NEW_PORT": str(self.target_port)})
|
||||
|
||||
websocket.WebSocketServer.__init__(self, RequestHandlerClass, *args, **kwargs)
|
||||
|
||||
def run_wrap_cmd(self):
|
||||
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
|
||||
self.wrap_times.append(time.time())
|
||||
self.wrap_times.pop(0)
|
||||
self.cmd = subprocess.Popen(
|
||||
self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
|
||||
self.spawn_message = True
|
||||
|
||||
def started(self):
|
||||
"""
|
||||
Called after Websockets server startup (i.e. after daemonize)
|
||||
"""
|
||||
# Need to call wrapped command after daemonization so we can
|
||||
# know when the wrapped command exits
|
||||
if self.wrap_cmd:
|
||||
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
|
||||
elif self.unix_target:
|
||||
dst_string = self.unix_target
|
||||
else:
|
||||
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
||||
|
||||
if self.target_cfg:
|
||||
msg = " - proxying from %s:%s to targets in %s" % (
|
||||
self.listen_host, self.listen_port, self.target_cfg)
|
||||
else:
|
||||
msg = " - proxying from %s:%s to %s" % (
|
||||
self.listen_host, self.listen_port, dst_string)
|
||||
|
||||
if self.ssl_target:
|
||||
msg += " (using SSL)"
|
||||
|
||||
self.msg("%s", msg)
|
||||
|
||||
if self.wrap_cmd:
|
||||
self.run_wrap_cmd()
|
||||
|
||||
def poll(self):
|
||||
# If we are wrapping a command, check it's status
|
||||
|
||||
if self.wrap_cmd and self.cmd:
|
||||
ret = self.cmd.poll()
|
||||
if ret != None:
|
||||
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
|
||||
self.cmd = None
|
||||
|
||||
if self.wrap_cmd and self.cmd == None:
|
||||
# Response to wrapped command being gone
|
||||
if self.wrap_mode == "ignore":
|
||||
pass
|
||||
elif self.wrap_mode == "exit":
|
||||
sys.exit(ret)
|
||||
elif self.wrap_mode == "respawn":
|
||||
now = time.time()
|
||||
avg = sum(self.wrap_times)/len(self.wrap_times)
|
||||
if (now - avg) < 10:
|
||||
# 3 times in the last 10 seconds
|
||||
if self.spawn_message:
|
||||
self.warn("Command respawning too fast")
|
||||
self.spawn_message = False
|
||||
else:
|
||||
self.run_wrap_cmd()
|
||||
|
||||
|
||||
def _subprocess_setup():
|
||||
# Python installs a SIGPIPE handler by default. This is usually not what
|
||||
# non-Python successfulbprocesses expect.
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
|
||||
|
||||
def logger_init():
|
||||
logger = logging.getLogger(WebSocketProxy.log_prefix)
|
||||
logger.propagate = False
|
||||
logger.setLevel(logging.INFO)
|
||||
h = logging.StreamHandler()
|
||||
h.setLevel(logging.DEBUG)
|
||||
h.setFormatter(logging.Formatter("%(message)s"))
|
||||
logger.addHandler(h)
|
||||
|
||||
|
||||
def websockify_init():
|
||||
logger_init()
|
||||
|
||||
usage = "\n %prog [options]"
|
||||
usage += " [source_addr:]source_port [target_addr:target_port]"
|
||||
usage += "\n %prog [options]"
|
||||
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
||||
parser = optparse.OptionParser(usage=usage)
|
||||
parser.add_option("--verbose", "-v", action="store_true",
|
||||
help="verbose messages")
|
||||
parser.add_option("--traffic", action="store_true",
|
||||
help="per frame traffic")
|
||||
parser.add_option("--record",
|
||||
help="record sessions to FILE.[session_number]", metavar="FILE")
|
||||
parser.add_option("--daemon", "-D",
|
||||
dest="daemon", action="store_true",
|
||||
help="become a daemon (background process)")
|
||||
parser.add_option("--run-once", action="store_true",
|
||||
help="handle a single WebSocket connection and exit")
|
||||
parser.add_option("--timeout", type=int, default=0,
|
||||
help="after TIMEOUT seconds exit when not connected")
|
||||
parser.add_option("--idle-timeout", type=int, default=0,
|
||||
help="server exits after TIMEOUT seconds if there are no "
|
||||
"active connections")
|
||||
parser.add_option("--cert", default="self.pem",
|
||||
help="SSL certificate file")
|
||||
parser.add_option("--key", default=None,
|
||||
help="SSL key file (if separate from cert)")
|
||||
parser.add_option("--ssl-only", action="store_true",
|
||||
help="disallow non-encrypted client connections")
|
||||
parser.add_option("--ssl-target", action="store_true",
|
||||
help="connect to SSL target as SSL client")
|
||||
parser.add_option("--unix-target",
|
||||
help="connect to unix socket target", metavar="FILE")
|
||||
parser.add_option("--web", default=None, metavar="DIR",
|
||||
help="run webserver on same port. Serve files from DIR.")
|
||||
parser.add_option("--wrap-mode", default="exit", metavar="MODE",
|
||||
choices=["exit", "ignore", "respawn"],
|
||||
help="action to take when the wrapped program exits "
|
||||
"or daemonizes: exit (default), ignore, respawn")
|
||||
parser.add_option("--prefer-ipv6", "-6",
|
||||
action="store_true", dest="source_is_ipv6",
|
||||
help="prefer IPv6 when resolving source_addr")
|
||||
parser.add_option("--target-config", metavar="FILE",
|
||||
dest="target_cfg",
|
||||
help="Configuration file containing valid targets "
|
||||
"in the form 'token: host:port' or, alternatively, a "
|
||||
"directory containing configuration files of this form")
|
||||
parser.add_option("--libserver", action="store_true",
|
||||
help="use Python library SocketServer engine")
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
if opts.verbose:
|
||||
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
|
||||
|
||||
# Sanity checks
|
||||
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
|
||||
parser.error("Too few arguments")
|
||||
if sys.argv.count('--'):
|
||||
opts.wrap_cmd = args[1:]
|
||||
else:
|
||||
opts.wrap_cmd = None
|
||||
if len(args) > 2:
|
||||
parser.error("Too many arguments")
|
||||
|
||||
if not websocket.ssl and opts.ssl_target:
|
||||
parser.error("SSL target requested and Python SSL module not loaded.");
|
||||
|
||||
if opts.ssl_only and not os.path.exists(opts.cert):
|
||||
parser.error("SSL only and %s not found" % opts.cert)
|
||||
|
||||
# Parse host:port and convert ports to numbers
|
||||
if args[0].count(':') > 0:
|
||||
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
|
||||
opts.listen_host = opts.listen_host.strip('[]')
|
||||
else:
|
||||
opts.listen_host, opts.listen_port = '', args[0]
|
||||
|
||||
try: opts.listen_port = int(opts.listen_port)
|
||||
except: parser.error("Error parsing listen port")
|
||||
|
||||
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
|
||||
opts.target_host = None
|
||||
opts.target_port = None
|
||||
else:
|
||||
if args[1].count(':') > 0:
|
||||
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
|
||||
opts.target_host = opts.target_host.strip('[]')
|
||||
else:
|
||||
parser.error("Error parsing target")
|
||||
try: opts.target_port = int(opts.target_port)
|
||||
except: parser.error("Error parsing target port")
|
||||
|
||||
# Transform to absolute path as daemon may chdir
|
||||
if opts.target_cfg:
|
||||
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
||||
|
||||
# Create and start the WebSockets proxy
|
||||
libserver = opts.libserver
|
||||
del opts.libserver
|
||||
if libserver:
|
||||
# Use standard Python SocketServer framework
|
||||
server = LibProxyServer(**opts.__dict__)
|
||||
server.serve_forever()
|
||||
else:
|
||||
# Use internal service framework
|
||||
server = WebSocketProxy(**opts.__dict__)
|
||||
server.start_server()
|
||||
|
||||
|
||||
class LibProxyServer(ForkingMixIn, HTTPServer):
|
||||
"""
|
||||
Just like WebSocketProxy, but uses standard Python SocketServer
|
||||
framework.
|
||||
"""
|
||||
|
||||
def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs):
|
||||
# Save off proxy specific options
|
||||
self.target_host = kwargs.pop('target_host', None)
|
||||
self.target_port = kwargs.pop('target_port', None)
|
||||
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
|
||||
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||
self.unix_target = kwargs.pop('unix_target', None)
|
||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||
self.target_cfg = kwargs.pop('target_cfg', None)
|
||||
self.daemon = False
|
||||
self.target_cfg = None
|
||||
|
||||
# Server configuration
|
||||
listen_host = kwargs.pop('listen_host', '')
|
||||
listen_port = kwargs.pop('listen_port', None)
|
||||
web = kwargs.pop('web', '')
|
||||
|
||||
# Configuration affecting base request handler
|
||||
self.only_upgrade = not web
|
||||
self.verbose = kwargs.pop('verbose', False)
|
||||
record = kwargs.pop('record', '')
|
||||
if record:
|
||||
self.record = os.path.abspath(record)
|
||||
self.run_once = kwargs.pop('run_once', False)
|
||||
self.handler_id = 0
|
||||
|
||||
for arg in kwargs.keys():
|
||||
print("warning: option %s ignored when using --libserver" % arg)
|
||||
|
||||
if web:
|
||||
os.chdir(web)
|
||||
|
||||
HTTPServer.__init__(self, (listen_host, listen_port),
|
||||
RequestHandlerClass)
|
||||
|
||||
|
||||
def process_request(self, request, client_address):
|
||||
"""Override process_request to implement a counter"""
|
||||
self.handler_id += 1
|
||||
ForkingMixIn.process_request(self, request, client_address)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
websockify_init()
|
||||
Reference in New Issue
Block a user