Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd4bc7590e | |||
| d626fed76a | |||
| 739af6ecb4 | |||
| 56b740ef5c | |||
| 18e70c52e1 | |||
| cc3ccf0ddd | |||
| 38db12c2b0 | |||
| a749611370 | |||
| b662d185ca | |||
| 6de6933819 | |||
| f46db48399 | |||
| 8049ddfe26 | |||
| f64e3dea51 | |||
| 17455afe4d | |||
| 047ce47742 | |||
| 558402848e | |||
| db93395061 | |||
| e246e98b20 | |||
| 7ecfa4f384 | |||
| bc216fb7d1 | |||
| b92528aeba | |||
| 81e2a53692 | |||
| 602248425a | |||
| 3b802c08b7 | |||
| 972b30ddc2 | |||
| f58b49fa08 | |||
| 131f9ea645 | |||
| cbf05f84fe | |||
| 611da86353 | |||
| fb4ac5ae51 | |||
| 063c7de783 | |||
| 08d37e0deb | |||
| ff30e5461f | |||
| 4071291fa9 | |||
| 611effd83b | |||
| b63d197292 | |||
| cf901ddac5 | |||
| 122985bd67 | |||
| 19c8482236 | |||
| 33a1bd2337 | |||
| 5f8cb96bab | |||
| 6ddfaa86ae | |||
| 4dc7c0da7a | |||
| 34a1b68d79 | |||
| a04edfe80f | |||
| 32c1abd5d9 | |||
| a47be21f9f | |||
| 5e0ff7d855 | |||
| c3acdc2e38 | |||
| 13c99bcf05 | |||
| b4e0b534d5 | |||
| be4119f84f | |||
| 32b0567343 | |||
| 82fe329129 | |||
| 0e5c3ecfda | |||
| 53f1f1989e | |||
| 081046b6cd | |||
| a61ae52610 | |||
| 477dce6cf8 | |||
| 8a0a47223d | |||
| 1f798214de | |||
| 4459824cc8 | |||
| c2a40d6900 | |||
| a7fa97f0e1 | |||
| 1190fe1204 | |||
| 7026e26d68 | |||
| 37e40a78f2 | |||
| edde5cd0ff | |||
| bff3c373b3 | |||
| f30ad05c70 | |||
| 354dd6b0a2 | |||
| e06de83295 | |||
| ab389d4e71 | |||
| eee946d2cf | |||
| 73af324a3a | |||
| 832118e61a | |||
| 264f8fdd7f | |||
| 36cb8f4676 | |||
| 36fcd5784f | |||
| 46450577c2 | |||
| d3ba23fa64 | |||
| 6d27b5d321 | |||
| 888e75a8fb | |||
| b7f255ce0b | |||
| 477947ba96 | |||
| 33e9a21ce4 | |||
| 2d078e8cd8 | |||
| d6d960dbe5 | |||
| ed109d7ec8 | |||
| bc917863e0 | |||
| e964c1edff | |||
| f5e42ff6f4 | |||
| f594d70daf | |||
| d0608a63b6 | |||
| 09f3ec7125 | |||
| 95593ac4bf | |||
| 1eecc7b17a | |||
| b05b773bd7 | |||
| debc926612 | |||
| 70eb75a3e6 | |||
| 903198a724 | |||
| 7b3dd8a6f5 | |||
| 208f83b9a2 | |||
| 4e3388964a | |||
| b9e1295f7a | |||
| d3865688c8 | |||
| 5a726c2e4d | |||
| db6a7e3e28 | |||
| d9aedfe7d3 | |||
| b2fe57c950 | |||
| 805026360e | |||
| 3d37d21b5f | |||
| c33f0b52e7 | |||
| 1d795c0643 | |||
| c00c0eed1a | |||
| 47fb367486 | |||
| 5e16b38524 | |||
| c728f43c63 | |||
| d1458d0063 | |||
| 66d772a6bc | |||
| a670af376e | |||
| 16691395e0 | |||
| 3018cf8c1a | |||
| 6a126405bd | |||
| ee2f269c06 | |||
| 780cb8a9e6 | |||
| 388d9573fd | |||
| 578dba1987 | |||
| 124c9a7d88 | |||
| e53fa10397 | |||
| 1295668abb | |||
| 880257a431 | |||
| 8b3125bcf3 | |||
| 471b504799 | |||
| b713acbeff | |||
| ff736e9ad3 | |||
| 384c2772fb | |||
| e6d8d8f242 | |||
| ca8efbf657 | |||
| eb6d4e183b | |||
| 6d9deda9c5 | |||
| 96890fab97 | |||
| c0d23e27e4 | |||
| 2d2798954e | |||
| 44e5fa0b82 | |||
| 82cb31f3b6 | |||
| 9348dd5208 |
@@ -4,5 +4,8 @@
|
||||
other/.lein-deps-sum
|
||||
other/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
|
||||
|
||||
+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();
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
+25
-21
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Websock: high-performance binary WebSockets
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* Websock is similar to the standard WebSocket object but Websock
|
||||
* enables communication with raw TCP sockets (i.e. the binary stream)
|
||||
@@ -35,23 +35,14 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
|
||||
Websock_native = false;
|
||||
(function () {
|
||||
function get_INCLUDE_URI() {
|
||||
return (typeof INCLUDE_URI !== "undefined") ?
|
||||
INCLUDE_URI : "include/";
|
||||
}
|
||||
|
||||
var start = "<script src='" + get_INCLUDE_URI(),
|
||||
end = "'><\/script>", extra = "";
|
||||
|
||||
window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
|
||||
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");
|
||||
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"]);
|
||||
}());
|
||||
}
|
||||
|
||||
@@ -61,7 +52,7 @@ function Websock() {
|
||||
|
||||
var api = {}, // Public API
|
||||
websocket = null, // WebSocket object
|
||||
mode = 'base64',
|
||||
mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
|
||||
rQ = [], // Receive queue
|
||||
rQi = 0, // Receive queue index
|
||||
rQmax = 10000, // Max receive queue size before compacting
|
||||
@@ -168,10 +159,11 @@ function rQwait(msg, num, goback) {
|
||||
//
|
||||
|
||||
function encode_message() {
|
||||
/* base64 encode */
|
||||
if (mode === 'binary') {
|
||||
// Put in a binary arraybuffer
|
||||
return (new Uint8Array(sQ)).buffer;
|
||||
} else {
|
||||
// base64 encode
|
||||
return Base64.encode(sQ);
|
||||
}
|
||||
}
|
||||
@@ -180,7 +172,10 @@ function decode_message(data) {
|
||||
//Util.Debug(">> decode_message: " + data);
|
||||
if (mode === 'binary') {
|
||||
// push arraybuffer values onto the end
|
||||
rQ.push.apply(rQ, (new Uint8Array(data)));
|
||||
var u8 = new Uint8Array(data);
|
||||
for (var i = 0; i < u8.length; i++) {
|
||||
rQ.push(u8[i]);
|
||||
}
|
||||
} else {
|
||||
// base64 decode and concat to the end
|
||||
rQ = rQ.concat(Base64.decode(data, 0));
|
||||
@@ -310,11 +305,19 @@ function init(protocols) {
|
||||
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') {
|
||||
throw("WebSocket binary sub-protocol requested but not supported");
|
||||
Util.Error("Skipping unsupported WebSocket binary sub-protocol");
|
||||
} else {
|
||||
new_protocols.push(protocols[i]);
|
||||
}
|
||||
}
|
||||
if (new_protocols.length > 0) {
|
||||
protocols = new_protocols;
|
||||
} else {
|
||||
throw("Only WebSocket binary sub-protocol was requested and not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +331,9 @@ function open(uri, protocols) {
|
||||
websocket = {};
|
||||
} else {
|
||||
websocket = new WebSocket(uri, protocols);
|
||||
if (protocols.indexOf('binary') >= 0) {
|
||||
websocket.binaryType = 'arraybuffer';
|
||||
}
|
||||
}
|
||||
|
||||
websocket.onmessage = recv_message;
|
||||
@@ -340,9 +346,6 @@ function open(uri, protocols) {
|
||||
mode = 'base64';
|
||||
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
|
||||
}
|
||||
if (mode === 'binary') {
|
||||
websocket.binaryType = 'arraybuffer';
|
||||
}
|
||||
eventHandlers.open();
|
||||
Util.Debug("<< WebSock.onopen");
|
||||
};
|
||||
@@ -371,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
|
||||
|
||||
+3
-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 \
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
-946
@@ -1,946 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Python WebSocket library with support for "wss://" encryption.
|
||||
Copyright 2011 Joel Martin
|
||||
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
Supports following protocol versions:
|
||||
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
|
||||
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
||||
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
||||
|
||||
You can make a cert/key with openssl using:
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import os, sys, time, errno, signal, socket, traceback, select
|
||||
import array, struct
|
||||
from base64 import b64encode, b64decode
|
||||
|
||||
# Imports that vary by python version
|
||||
|
||||
# python 3.0 differences
|
||||
if sys.hexversion > 0x3000000:
|
||||
b2s = lambda buf: buf.decode('latin_1')
|
||||
s2b = lambda s: s.encode('latin_1')
|
||||
s2a = lambda s: s
|
||||
else:
|
||||
b2s = lambda buf: buf # No-op
|
||||
s2b = lambda s: s # No-op
|
||||
s2a = lambda s: [ord(c) for c in s]
|
||||
try: from io import StringIO
|
||||
except: from cStringIO import StringIO
|
||||
try: from http.server import SimpleHTTPRequestHandler
|
||||
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
|
||||
# python 2.6 differences
|
||||
try: from hashlib import md5, sha1
|
||||
except: from md5 import md5; from sha import sha as sha1
|
||||
|
||||
# python 2.5 differences
|
||||
try:
|
||||
from struct import pack, unpack_from
|
||||
except:
|
||||
from struct import pack
|
||||
def unpack_from(fmt, buf, offset=0):
|
||||
slice = buffer(buf, offset, struct.calcsize(fmt))
|
||||
return struct.unpack(fmt, slice)
|
||||
|
||||
# Degraded functionality if these imports are missing
|
||||
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
|
||||
('multiprocessing', 'Multi-Processing'),
|
||||
('resource', 'daemonizing')]:
|
||||
try:
|
||||
globals()[mod] = __import__(mod)
|
||||
except ImportError:
|
||||
globals()[mod] = None
|
||||
print("WARNING: no '%s' module, %s is slower or disabled" % (
|
||||
mod, sup))
|
||||
if multiprocessing and sys.platform == 'win32':
|
||||
# make sockets pickle-able/inheritable
|
||||
import multiprocessing.reduction
|
||||
|
||||
|
||||
class WebSocketServer(object):
|
||||
"""
|
||||
WebSockets server class.
|
||||
Must be sub-classed with new_client method definition.
|
||||
"""
|
||||
|
||||
buffer_size = 65536
|
||||
|
||||
|
||||
server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
||||
Upgrade: WebSocket\r
|
||||
Connection: Upgrade\r
|
||||
%sWebSocket-Origin: %s\r
|
||||
%sWebSocket-Location: %s://%s%s\r
|
||||
"""
|
||||
|
||||
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
|
||||
Upgrade: websocket\r
|
||||
Connection: Upgrade\r
|
||||
Sec-WebSocket-Accept: %s\r
|
||||
"""
|
||||
|
||||
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
|
||||
|
||||
# An exception before the WebSocket connection was established
|
||||
class EClose(Exception):
|
||||
pass
|
||||
|
||||
# An exception while the WebSocket client was connected
|
||||
class CClose(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
|
||||
verbose=False, cert='', key='', ssl_only=None,
|
||||
daemon=False, record='', web='',
|
||||
run_once=False, timeout=0):
|
||||
|
||||
# settings
|
||||
self.verbose = verbose
|
||||
self.listen_host = listen_host
|
||||
self.listen_port = listen_port
|
||||
self.prefer_ipv6 = source_is_ipv6
|
||||
self.ssl_only = ssl_only
|
||||
self.daemon = daemon
|
||||
self.run_once = run_once
|
||||
self.timeout = timeout
|
||||
|
||||
self.launch_time = time.time()
|
||||
self.ws_connection = False
|
||||
self.handler_id = 1
|
||||
|
||||
# Make paths settings absolute
|
||||
self.cert = os.path.abspath(cert)
|
||||
self.key = self.web = self.record = ''
|
||||
if key:
|
||||
self.key = os.path.abspath(key)
|
||||
if web:
|
||||
self.web = os.path.abspath(web)
|
||||
if record:
|
||||
self.record = os.path.abspath(record)
|
||||
|
||||
if self.web:
|
||||
os.chdir(self.web)
|
||||
|
||||
# Sanity checks
|
||||
if not ssl and self.ssl_only:
|
||||
raise Exception("No 'ssl' module and SSL-only specified")
|
||||
if self.daemon and not resource:
|
||||
raise Exception("Module 'resource' required to daemonize")
|
||||
|
||||
# Show configuration
|
||||
print("WebSocket server settings:")
|
||||
print(" - Listen on %s:%s" % (
|
||||
self.listen_host, self.listen_port))
|
||||
print(" - Flash security policy server")
|
||||
if self.web:
|
||||
print(" - Web server. Web root: %s" % self.web)
|
||||
if ssl:
|
||||
if os.path.exists(self.cert):
|
||||
print(" - SSL/TLS support")
|
||||
if self.ssl_only:
|
||||
print(" - Deny non-SSL/TLS connections")
|
||||
else:
|
||||
print(" - No SSL/TLS support (no cert file)")
|
||||
else:
|
||||
print(" - No SSL/TLS support (no 'ssl' module)")
|
||||
if self.daemon:
|
||||
print(" - Backgrounding (daemon)")
|
||||
if self.record:
|
||||
print(" - Recording to '%s.*'" % self.record)
|
||||
|
||||
#
|
||||
# WebSocketServer static methods
|
||||
#
|
||||
|
||||
@staticmethod
|
||||
def socket(host, port=None, connect=False, prefer_ipv6=False, unix_socket=None, use_ssl=False):
|
||||
""" Resolve a host (and optional port) to an IPv4 or IPv6
|
||||
address. Create a socket. Bind to it if listen is set,
|
||||
otherwise connect to it. Return the socket.
|
||||
"""
|
||||
flags = 0
|
||||
if host == '':
|
||||
host = None
|
||||
if connect and not (port or unix_socket):
|
||||
raise Exception("Connect mode requires a port")
|
||||
if use_ssl and not ssl:
|
||||
raise Exception("SSL socket requested but Python SSL module not loaded.");
|
||||
if not connect and use_ssl:
|
||||
raise Exception("SSL only supported in connect mode (for now)")
|
||||
if not connect:
|
||||
flags = flags | socket.AI_PASSIVE
|
||||
|
||||
if not unix_socket:
|
||||
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
|
||||
socket.IPPROTO_TCP, flags)
|
||||
if not addrs:
|
||||
raise Exception("Could not resolve host '%s'" % host)
|
||||
addrs.sort(key=lambda x: x[0])
|
||||
if prefer_ipv6:
|
||||
addrs.reverse()
|
||||
sock = socket.socket(addrs[0][0], addrs[0][1])
|
||||
if connect:
|
||||
sock.connect(addrs[0][4])
|
||||
if use_ssl:
|
||||
sock = ssl.wrap_socket(sock)
|
||||
else:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind(addrs[0][4])
|
||||
sock.listen(100)
|
||||
else:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(unix_socket)
|
||||
|
||||
return sock
|
||||
|
||||
@staticmethod
|
||||
def daemonize(keepfd=None, chdir='/'):
|
||||
os.umask(0)
|
||||
if chdir:
|
||||
os.chdir(chdir)
|
||||
else:
|
||||
os.chdir('/')
|
||||
os.setgid(os.getgid()) # relinquish elevations
|
||||
os.setuid(os.getuid()) # relinquish elevations
|
||||
|
||||
# Double fork to daemonize
|
||||
if os.fork() > 0: os._exit(0) # Parent exits
|
||||
os.setsid() # Obtain new process group
|
||||
if os.fork() > 0: os._exit(0) # Parent exits
|
||||
|
||||
# Signal handling
|
||||
def terminate(a,b): os._exit(0)
|
||||
signal.signal(signal.SIGTERM, terminate)
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
# Close open files
|
||||
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
||||
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
||||
for fd in reversed(range(maxfd)):
|
||||
try:
|
||||
if fd != keepfd:
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
_, exc, _ = sys.exc_info()
|
||||
if exc.errno != errno.EBADF: raise
|
||||
|
||||
# Redirect I/O to /dev/null
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
|
||||
|
||||
@staticmethod
|
||||
def unmask(buf, f):
|
||||
pstart = f['hlen'] + 4
|
||||
pend = pstart + f['length']
|
||||
if numpy:
|
||||
b = c = s2b('')
|
||||
if f['length'] >= 4:
|
||||
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
||||
offset=f['hlen'], count=1)
|
||||
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
||||
offset=pstart, count=int(f['length'] / 4))
|
||||
#b = numpy.bitwise_xor(data, mask).data
|
||||
b = numpy.bitwise_xor(data, mask).tostring()
|
||||
|
||||
if f['length'] % 4:
|
||||
#print("Partial unmask")
|
||||
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
||||
offset=f['hlen'], count=(f['length'] % 4))
|
||||
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
||||
offset=pend - (f['length'] % 4),
|
||||
count=(f['length'] % 4))
|
||||
c = numpy.bitwise_xor(data, mask).tostring()
|
||||
return b + c
|
||||
else:
|
||||
# Slower fallback
|
||||
data = array.array('B')
|
||||
mask = s2a(f['mask'])
|
||||
data.fromstring(buf[pstart:pend])
|
||||
for i in range(len(data)):
|
||||
data[i] ^= mask[i % 4]
|
||||
return data.tostring()
|
||||
|
||||
@staticmethod
|
||||
def encode_hybi(buf, opcode, base64=False):
|
||||
""" Encode a HyBi style WebSocket frame.
|
||||
Optional opcode:
|
||||
0x0 - continuation
|
||||
0x1 - text frame (base64 encode buf)
|
||||
0x2 - binary frame (use raw buf)
|
||||
0x8 - connection close
|
||||
0x9 - ping
|
||||
0xA - pong
|
||||
"""
|
||||
if base64:
|
||||
buf = b64encode(buf)
|
||||
|
||||
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
|
||||
payload_len = len(buf)
|
||||
if payload_len <= 125:
|
||||
header = pack('>BB', b1, payload_len)
|
||||
elif payload_len > 125 and payload_len < 65536:
|
||||
header = pack('>BBH', b1, 126, payload_len)
|
||||
elif payload_len >= 65536:
|
||||
header = pack('>BBQ', b1, 127, payload_len)
|
||||
|
||||
#print("Encoded: %s" % repr(header + buf))
|
||||
|
||||
return header + buf, len(header), 0
|
||||
|
||||
@staticmethod
|
||||
def decode_hybi(buf, base64=False):
|
||||
""" Decode HyBi style WebSocket packets.
|
||||
Returns:
|
||||
{'fin' : 0_or_1,
|
||||
'opcode' : number,
|
||||
'mask' : 32_bit_number,
|
||||
'hlen' : header_bytes_number,
|
||||
'length' : payload_bytes_number,
|
||||
'payload' : decoded_buffer,
|
||||
'left' : bytes_left_number,
|
||||
'close_code' : number,
|
||||
'close_reason' : string}
|
||||
"""
|
||||
|
||||
f = {'fin' : 0,
|
||||
'opcode' : 0,
|
||||
'mask' : 0,
|
||||
'hlen' : 2,
|
||||
'length' : 0,
|
||||
'payload' : None,
|
||||
'left' : 0,
|
||||
'close_code' : 1000,
|
||||
'close_reason' : ''}
|
||||
|
||||
blen = len(buf)
|
||||
f['left'] = blen
|
||||
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
|
||||
b1, b2 = unpack_from(">BB", buf)
|
||||
f['opcode'] = b1 & 0x0f
|
||||
f['fin'] = (b1 & 0x80) >> 7
|
||||
has_mask = (b2 & 0x80) >> 7
|
||||
|
||||
f['length'] = b2 & 0x7f
|
||||
|
||||
if f['length'] == 126:
|
||||
f['hlen'] = 4
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = unpack_from('>xxH', buf)
|
||||
elif f['length'] == 127:
|
||||
f['hlen'] = 10
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = unpack_from('>xxQ', buf)
|
||||
|
||||
full_len = f['hlen'] + has_mask * 4 + f['length']
|
||||
|
||||
if blen < full_len: # Incomplete frame
|
||||
return f # Incomplete frame header
|
||||
|
||||
# Number of bytes that are part of the next frame(s)
|
||||
f['left'] = blen - full_len
|
||||
|
||||
# Process 1 frame
|
||||
if has_mask:
|
||||
# unmask payload
|
||||
f['mask'] = buf[f['hlen']:f['hlen']+4]
|
||||
f['payload'] = WebSocketServer.unmask(buf, f)
|
||||
else:
|
||||
print("Unmasked frame: %s" % repr(buf))
|
||||
f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
|
||||
|
||||
if base64 and f['opcode'] in [1, 2]:
|
||||
try:
|
||||
f['payload'] = b64decode(f['payload'])
|
||||
except:
|
||||
print("Exception while b64decoding buffer: %s" %
|
||||
repr(buf))
|
||||
raise
|
||||
|
||||
if f['opcode'] == 0x08:
|
||||
if f['length'] >= 2:
|
||||
f['close_code'] = unpack_from(">H", f['payload'])[0]
|
||||
if f['length'] > 3:
|
||||
f['close_reason'] = f['payload'][2:]
|
||||
|
||||
return f
|
||||
|
||||
@staticmethod
|
||||
def encode_hixie(buf):
|
||||
return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
|
||||
|
||||
@staticmethod
|
||||
def decode_hixie(buf):
|
||||
end = buf.find(s2b('\xff'))
|
||||
return {'payload': b64decode(buf[1:end]),
|
||||
'hlen': 1,
|
||||
'length': end - 1,
|
||||
'left': len(buf) - (end + 1)}
|
||||
|
||||
|
||||
@staticmethod
|
||||
def gen_md5(keys):
|
||||
""" Generate hash value for WebSockets hixie-76. """
|
||||
key1 = keys['Sec-WebSocket-Key1']
|
||||
key2 = keys['Sec-WebSocket-Key2']
|
||||
key3 = keys['key3']
|
||||
spaces1 = key1.count(" ")
|
||||
spaces2 = key2.count(" ")
|
||||
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
|
||||
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
|
||||
|
||||
return b2s(md5(pack('>II8s',
|
||||
int(num1), int(num2), key3)).digest())
|
||||
|
||||
#
|
||||
# WebSocketServer logging/output functions
|
||||
#
|
||||
|
||||
def traffic(self, token="."):
|
||||
""" Show traffic flow in verbose mode. """
|
||||
if self.verbose and not self.daemon:
|
||||
sys.stdout.write(token)
|
||||
sys.stdout.flush()
|
||||
|
||||
def msg(self, msg):
|
||||
""" Output message with handler_id prefix. """
|
||||
if not self.daemon:
|
||||
print("% 3d: %s" % (self.handler_id, msg))
|
||||
|
||||
def vmsg(self, msg):
|
||||
""" Same as msg() but only if verbose. """
|
||||
if self.verbose:
|
||||
self.msg(msg)
|
||||
|
||||
#
|
||||
# Main WebSocketServer methods
|
||||
#
|
||||
def send_frames(self, bufs=None):
|
||||
""" Encode and send WebSocket frames. Any frames already
|
||||
queued will be sent first. If buf is not set then only queued
|
||||
frames will be sent. Returns the number of pending frames that
|
||||
could not be fully sent. If returned pending frames is greater
|
||||
than 0, then the caller should call again when the socket is
|
||||
ready. """
|
||||
|
||||
tdelta = int(time.time()*1000) - self.start_time
|
||||
|
||||
if bufs:
|
||||
for buf in bufs:
|
||||
if self.version.startswith("hybi"):
|
||||
if self.base64:
|
||||
encbuf, lenhead, lentail = self.encode_hybi(
|
||||
buf, opcode=1, base64=True)
|
||||
else:
|
||||
encbuf, lenhead, lentail = self.encode_hybi(
|
||||
buf, opcode=2, base64=False)
|
||||
|
||||
else:
|
||||
encbuf, lenhead, lentail = self.encode_hixie(buf)
|
||||
|
||||
if self.rec:
|
||||
self.rec.write("%s,\n" %
|
||||
repr("{%s{" % tdelta
|
||||
+ encbuf[lenhead:-lentail]))
|
||||
|
||||
self.send_parts.append(encbuf)
|
||||
|
||||
while self.send_parts:
|
||||
# Send pending frames
|
||||
buf = self.send_parts.pop(0)
|
||||
sent = self.client.send(buf)
|
||||
|
||||
if sent == len(buf):
|
||||
self.traffic("<")
|
||||
else:
|
||||
self.traffic("<.")
|
||||
self.send_parts.insert(0, buf[sent:])
|
||||
break
|
||||
|
||||
return len(self.send_parts)
|
||||
|
||||
def recv_frames(self):
|
||||
""" Receive and decode WebSocket frames.
|
||||
|
||||
Returns:
|
||||
(bufs_list, closed_string)
|
||||
"""
|
||||
|
||||
closed = False
|
||||
bufs = []
|
||||
tdelta = int(time.time()*1000) - self.start_time
|
||||
|
||||
buf = self.client.recv(self.buffer_size)
|
||||
if len(buf) == 0:
|
||||
closed = {'code': 1000, 'reason': "Client closed abruptly"}
|
||||
return bufs, closed
|
||||
|
||||
if self.recv_part:
|
||||
# Add partially received frames to current read buffer
|
||||
buf = self.recv_part + buf
|
||||
self.recv_part = None
|
||||
|
||||
while buf:
|
||||
if self.version.startswith("hybi"):
|
||||
|
||||
frame = self.decode_hybi(buf, base64=self.base64)
|
||||
#print("Received buf: %s, frame: %s" % (repr(buf), frame))
|
||||
|
||||
if frame['payload'] == None:
|
||||
# Incomplete/partial frame
|
||||
self.traffic("}.")
|
||||
if frame['left'] > 0:
|
||||
self.recv_part = buf[-frame['left']:]
|
||||
break
|
||||
else:
|
||||
if frame['opcode'] == 0x8: # connection close
|
||||
closed = {'code': frame['close_code'],
|
||||
'reason': frame['close_reason']}
|
||||
break
|
||||
|
||||
else:
|
||||
if buf[0:2] == s2b('\xff\x00'):
|
||||
closed = {'code': 1000,
|
||||
'reason': "Client sent orderly close frame"}
|
||||
break
|
||||
|
||||
elif buf[0:2] == s2b('\x00\xff'):
|
||||
buf = buf[2:]
|
||||
continue # No-op
|
||||
|
||||
elif buf.count(s2b('\xff')) == 0:
|
||||
# Partial frame
|
||||
self.traffic("}.")
|
||||
self.recv_part = buf
|
||||
break
|
||||
|
||||
frame = self.decode_hixie(buf)
|
||||
|
||||
self.traffic("}")
|
||||
|
||||
if self.rec:
|
||||
start = frame['hlen']
|
||||
end = frame['hlen'] + frame['length']
|
||||
self.rec.write("%s,\n" %
|
||||
repr("}%s}" % tdelta + buf[start:end]))
|
||||
|
||||
|
||||
bufs.append(frame['payload'])
|
||||
|
||||
if frame['left']:
|
||||
buf = buf[-frame['left']:]
|
||||
else:
|
||||
buf = ''
|
||||
|
||||
return bufs, closed
|
||||
|
||||
def send_close(self, code=1000, reason=''):
|
||||
""" Send a WebSocket orderly close frame. """
|
||||
|
||||
if self.version.startswith("hybi"):
|
||||
msg = pack(">H%ds" % len(reason), code, reason)
|
||||
|
||||
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
||||
self.client.send(buf)
|
||||
|
||||
elif self.version == "hixie-76":
|
||||
buf = s2b('\xff\x00')
|
||||
self.client.send(buf)
|
||||
|
||||
# No orderly close for 75
|
||||
|
||||
def do_websocket_handshake(self, headers, path):
|
||||
h = self.headers = headers
|
||||
self.path = path
|
||||
|
||||
prot = 'WebSocket-Protocol'
|
||||
protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
|
||||
|
||||
ver = h.get('Sec-WebSocket-Version')
|
||||
if ver:
|
||||
# HyBi/IETF version of the protocol
|
||||
|
||||
# HyBi-07 report version 7
|
||||
# HyBi-08 - HyBi-12 report version 8
|
||||
# HyBi-13 reports version 13
|
||||
if ver in ['7', '8', '13']:
|
||||
self.version = "hybi-%02d" % int(ver)
|
||||
else:
|
||||
raise self.EClose('Unsupported protocol version %s' % ver)
|
||||
|
||||
key = h['Sec-WebSocket-Key']
|
||||
|
||||
# Choose binary if client supports it
|
||||
if 'binary' in protocols:
|
||||
self.base64 = False
|
||||
elif 'base64' in protocols:
|
||||
self.base64 = True
|
||||
else:
|
||||
raise self.EClose("Client must support 'binary' or 'base64' protocol")
|
||||
|
||||
# Generate the hash value for the accept header
|
||||
accept = b64encode(sha1(s2b(key + self.GUID)).digest())
|
||||
|
||||
response = self.server_handshake_hybi % b2s(accept)
|
||||
if self.base64:
|
||||
response += "Sec-WebSocket-Protocol: base64\r\n"
|
||||
else:
|
||||
response += "Sec-WebSocket-Protocol: binary\r\n"
|
||||
response += "\r\n"
|
||||
|
||||
else:
|
||||
# Hixie version of the protocol (75 or 76)
|
||||
|
||||
if h.get('key3'):
|
||||
trailer = self.gen_md5(h)
|
||||
pre = "Sec-"
|
||||
self.version = "hixie-76"
|
||||
else:
|
||||
trailer = ""
|
||||
pre = ""
|
||||
self.version = "hixie-75"
|
||||
|
||||
# We only support base64 in Hixie era
|
||||
self.base64 = True
|
||||
|
||||
response = self.server_handshake_hixie % (pre,
|
||||
h['Origin'], pre, self.scheme, h['Host'], path)
|
||||
|
||||
if 'base64' in protocols:
|
||||
response += "%sWebSocket-Protocol: base64\r\n" % pre
|
||||
else:
|
||||
self.msg("Warning: client does not report 'base64' protocol support")
|
||||
response += "\r\n" + trailer
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def do_handshake(self, sock, address):
|
||||
"""
|
||||
do_handshake does the following:
|
||||
- Peek at the first few bytes from the socket.
|
||||
- If the connection is Flash policy request then answer it,
|
||||
close the socket and return.
|
||||
- If the connection is an HTTPS/SSL/TLS connection then SSL
|
||||
wrap the socket.
|
||||
- Read from the (possibly wrapped) socket.
|
||||
- If we have received a HTTP GET request and the webserver
|
||||
functionality is enabled, answer it, close the socket and
|
||||
return.
|
||||
- Assume we have a WebSockets connection, parse the client
|
||||
handshake data.
|
||||
- Send a WebSockets handshake server response.
|
||||
- Return the socket for this WebSocket client.
|
||||
"""
|
||||
stype = ""
|
||||
ready = select.select([sock], [], [], 3)[0]
|
||||
|
||||
|
||||
if not ready:
|
||||
raise self.EClose("ignoring socket not ready")
|
||||
# Peek, but do not read the data so that we have a opportunity
|
||||
# to SSL wrap the socket first
|
||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
||||
#self.msg("Handshake [%s]" % handshake)
|
||||
|
||||
if handshake == "":
|
||||
raise self.EClose("ignoring empty handshake")
|
||||
|
||||
elif handshake.startswith(s2b("<policy-file-request/>")):
|
||||
# Answer Flash policy request
|
||||
handshake = sock.recv(1024)
|
||||
sock.send(s2b(self.policy_response))
|
||||
raise self.EClose("Sending flash policy response")
|
||||
|
||||
elif handshake[0] in ("\x16", "\x80", 22, 128):
|
||||
# SSL wrap the connection
|
||||
if not ssl:
|
||||
raise self.EClose("SSL connection but no 'ssl' module")
|
||||
if not os.path.exists(self.cert):
|
||||
raise self.EClose("SSL connection but '%s' not found"
|
||||
% self.cert)
|
||||
retsock = None
|
||||
try:
|
||||
retsock = ssl.wrap_socket(
|
||||
sock,
|
||||
server_side=True,
|
||||
certfile=self.cert,
|
||||
keyfile=self.key)
|
||||
except ssl.SSLError:
|
||||
_, x, _ = sys.exc_info()
|
||||
if x.args[0] == ssl.SSL_ERROR_EOF:
|
||||
if len(x.args) > 1:
|
||||
raise self.EClose(x.args[1])
|
||||
else:
|
||||
raise self.EClose("Got SSL_ERROR_EOF")
|
||||
else:
|
||||
raise
|
||||
|
||||
self.scheme = "wss"
|
||||
stype = "SSL/TLS (wss://)"
|
||||
|
||||
elif self.ssl_only:
|
||||
raise self.EClose("non-SSL connection received but disallowed")
|
||||
|
||||
else:
|
||||
retsock = sock
|
||||
self.scheme = "ws"
|
||||
stype = "Plain non-SSL (ws://)"
|
||||
|
||||
wsh = WSRequestHandler(retsock, address, not self.web)
|
||||
if wsh.last_code == 101:
|
||||
# Continue on to handle WebSocket upgrade
|
||||
pass
|
||||
elif wsh.last_code == 405:
|
||||
raise self.EClose("Normal web request received but disallowed")
|
||||
elif wsh.last_code < 200 or wsh.last_code >= 300:
|
||||
raise self.EClose(wsh.last_message)
|
||||
elif self.verbose:
|
||||
raise self.EClose(wsh.last_message)
|
||||
else:
|
||||
raise self.EClose("")
|
||||
|
||||
response = self.do_websocket_handshake(wsh.headers, wsh.path)
|
||||
|
||||
self.msg("%s: %s WebSocket connection" % (address[0], stype))
|
||||
self.msg("%s: Version %s, base64: '%s'" % (address[0],
|
||||
self.version, self.base64))
|
||||
if self.path != '/':
|
||||
self.msg("%s: Path: '%s'" % (address[0], self.path))
|
||||
|
||||
|
||||
# Send server WebSockets handshake response
|
||||
#self.msg("sending response [%s]" % response)
|
||||
retsock.send(s2b(response))
|
||||
|
||||
# Return the WebSockets socket which may be SSL wrapped
|
||||
return retsock
|
||||
|
||||
|
||||
#
|
||||
# Events that can/should be overridden in sub-classes
|
||||
#
|
||||
def started(self):
|
||||
""" Called after WebSockets startup """
|
||||
self.vmsg("WebSockets server started")
|
||||
|
||||
def poll(self):
|
||||
""" Run periodically while waiting for connections. """
|
||||
#self.vmsg("Running poll()")
|
||||
pass
|
||||
|
||||
def fallback_SIGCHLD(self, sig, stack):
|
||||
# Reap zombies when using os.fork() (python 2.4)
|
||||
self.vmsg("Got SIGCHLD, reaping zombies")
|
||||
try:
|
||||
result = os.waitpid(-1, os.WNOHANG)
|
||||
while result[0]:
|
||||
self.vmsg("Reaped child process %s" % result[0])
|
||||
result = os.waitpid(-1, os.WNOHANG)
|
||||
except (OSError):
|
||||
pass
|
||||
|
||||
def do_SIGINT(self, sig, stack):
|
||||
self.msg("Got SIGINT, exiting")
|
||||
sys.exit(0)
|
||||
|
||||
def top_new_client(self, startsock, address):
|
||||
""" Do something with a WebSockets client connection. """
|
||||
# Initialize per client settings
|
||||
self.send_parts = []
|
||||
self.recv_part = None
|
||||
self.base64 = False
|
||||
self.rec = None
|
||||
self.start_time = int(time.time()*1000)
|
||||
|
||||
# handler process
|
||||
try:
|
||||
try:
|
||||
self.client = self.do_handshake(startsock, address)
|
||||
|
||||
if self.record:
|
||||
# Record raw frame data as JavaScript array
|
||||
fname = "%s.%s" % (self.record,
|
||||
self.handler_id)
|
||||
self.msg("opening record file: %s" % fname)
|
||||
self.rec = open(fname, 'w+')
|
||||
self.rec.write("var VNC_frame_data = [\n")
|
||||
|
||||
self.ws_connection = True
|
||||
self.new_client()
|
||||
except self.CClose:
|
||||
# Close the client
|
||||
_, exc, _ = sys.exc_info()
|
||||
if self.client:
|
||||
self.send_close(exc.args[0], exc.args[1])
|
||||
except self.EClose:
|
||||
_, exc, _ = sys.exc_info()
|
||||
# Connection was not a WebSockets connection
|
||||
if exc.args[0]:
|
||||
self.msg("%s: %s" % (address[0], exc.args[0]))
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
self.msg("handler exception: %s" % str(exc))
|
||||
if self.verbose:
|
||||
self.msg(traceback.format_exc())
|
||||
finally:
|
||||
if self.rec:
|
||||
self.rec.write("'EOF']\n")
|
||||
self.rec.close()
|
||||
|
||||
if self.client and self.client != startsock:
|
||||
# Close the SSL wrapped socket
|
||||
# Original socket closed by caller
|
||||
self.client.close()
|
||||
|
||||
def new_client(self):
|
||||
""" Do something with a WebSockets client connection. """
|
||||
raise("WebSocketServer.new_client() must be overloaded")
|
||||
|
||||
def start_server(self):
|
||||
"""
|
||||
Daemonize if requested. Listen for for connections. Run
|
||||
do_handshake() method for each connection. If the connection
|
||||
is a WebSockets client then call new_client() method (which must
|
||||
be overridden) for each new client connection.
|
||||
"""
|
||||
lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6)
|
||||
|
||||
if self.daemon:
|
||||
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
|
||||
|
||||
self.started() # Some things need to happen after daemonizing
|
||||
|
||||
# Allow override of SIGINT
|
||||
signal.signal(signal.SIGINT, self.do_SIGINT)
|
||||
if not multiprocessing:
|
||||
# os.fork() (python 2.4) child reaper
|
||||
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
|
||||
|
||||
while True:
|
||||
try:
|
||||
try:
|
||||
self.client = None
|
||||
startsock = None
|
||||
pid = err = 0
|
||||
|
||||
time_elapsed = time.time() - self.launch_time
|
||||
if self.timeout and time_elapsed > self.timeout:
|
||||
self.msg('listener exit due to --timeout %s'
|
||||
% self.timeout)
|
||||
break
|
||||
|
||||
try:
|
||||
self.poll()
|
||||
|
||||
ready = select.select([lsock], [], [], 1)[0]
|
||||
if lsock in ready:
|
||||
startsock, address = lsock.accept()
|
||||
else:
|
||||
continue
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
if hasattr(exc, 'errno'):
|
||||
err = exc.errno
|
||||
elif hasattr(exc, 'args'):
|
||||
err = exc.args[0]
|
||||
else:
|
||||
err = exc[0]
|
||||
if err == errno.EINTR:
|
||||
self.vmsg("Ignoring interrupted syscall")
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
if self.run_once:
|
||||
# Run in same process if run_once
|
||||
self.top_new_client(startsock, address)
|
||||
if self.ws_connection :
|
||||
self.msg('%s: exiting due to --run-once'
|
||||
% address[0])
|
||||
break
|
||||
elif multiprocessing:
|
||||
self.vmsg('%s: new handler Process' % address[0])
|
||||
p = multiprocessing.Process(
|
||||
target=self.top_new_client,
|
||||
args=(startsock, address))
|
||||
p.start()
|
||||
# child will not return
|
||||
else:
|
||||
# python 2.4
|
||||
self.vmsg('%s: forking handler' % address[0])
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
# child handler process
|
||||
self.top_new_client(startsock, address)
|
||||
break # child process exits
|
||||
|
||||
# parent process
|
||||
self.handler_id += 1
|
||||
|
||||
except KeyboardInterrupt:
|
||||
_, exc, _ = sys.exc_info()
|
||||
print("In KeyboardInterrupt")
|
||||
pass
|
||||
except SystemExit:
|
||||
_, exc, _ = sys.exc_info()
|
||||
print("In SystemExit")
|
||||
break
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
self.msg("handler exception: %s" % str(exc))
|
||||
if self.verbose:
|
||||
self.msg(traceback.format_exc())
|
||||
|
||||
finally:
|
||||
if startsock:
|
||||
startsock.close()
|
||||
|
||||
|
||||
# HTTP handler with WebSocket upgrade support
|
||||
class WSRequestHandler(SimpleHTTPRequestHandler):
|
||||
def __init__(self, req, addr, only_upgrade=False):
|
||||
self.only_upgrade = only_upgrade # only allow upgrades
|
||||
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
|
||||
|
||||
def do_GET(self):
|
||||
if (self.headers.get('upgrade') and
|
||||
self.headers.get('upgrade').lower() == 'websocket'):
|
||||
|
||||
if (self.headers.get('sec-websocket-key1') or
|
||||
self.headers.get('websocket-key1')):
|
||||
# For Hixie-76 read out the key hash
|
||||
self.headers.__setitem__('key3', self.rfile.read(8))
|
||||
|
||||
# Just indicate that an WebSocket upgrade is needed
|
||||
self.last_code = 101
|
||||
self.last_message = "101 Switching Protocols"
|
||||
elif self.only_upgrade:
|
||||
# Normal web request responses are disabled
|
||||
self.last_code = 405
|
||||
self.last_message = "405 Method Not Allowed"
|
||||
else:
|
||||
SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
def send_response(self, code, message=None):
|
||||
# Save the status code
|
||||
self.last_code = code
|
||||
SimpleHTTPRequestHandler.send_response(self, code, message)
|
||||
|
||||
def log_message(self, f, *args):
|
||||
# Save instead of printing
|
||||
self.last_message = f % args
|
||||
+1
-1
@@ -1 +1 @@
|
||||
websockify
|
||||
run
|
||||
@@ -0,0 +1,2 @@
|
||||
from websocket import *
|
||||
from websocketproxy import *
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,21 +11,20 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import socket, optparse, time, os, sys, subprocess
|
||||
import signal, socket, optparse, time, os, sys, subprocess, logging
|
||||
try: from socketserver import ForkingMixIn
|
||||
except: from SocketServer import ForkingMixIn
|
||||
try: from http.server import HTTPServer
|
||||
except: from BaseHTTPServer import HTTPServer
|
||||
from select import select
|
||||
import websocket
|
||||
try: from urllib.parse import parse_qs, urlparse
|
||||
except: from urlparse import parse_qs, urlparse
|
||||
try:
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
except:
|
||||
from cgi import parse_qs
|
||||
from urlparse import urlparse
|
||||
|
||||
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
|
||||
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
|
||||
|
||||
traffic_legend = """
|
||||
Traffic Legend:
|
||||
@@ -39,148 +38,33 @@ Traffic Legend:
|
||||
<. - 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')
|
||||
self.unix_target = kwargs.pop('unix_target')
|
||||
self.ssl_target = kwargs.pop('ssl_target')
|
||||
self.target_cfg = kwargs.pop('target_cfg')
|
||||
# Last 3 timestamps command was run
|
||||
self.wrap_times = [0, 0, 0]
|
||||
|
||||
if self.wrap_cmd:
|
||||
rebinder_path = ['./', os.path.dirname(sys.argv[0])]
|
||||
self.rebinder = None
|
||||
|
||||
for rdir in rebinder_path:
|
||||
rpath = os.path.join(rdir, "rebind.so")
|
||||
if os.path.exists(rpath):
|
||||
self.rebinder = rpath
|
||||
break
|
||||
|
||||
if not self.rebinder:
|
||||
raise Exception("rebind.so not found, perhaps you need to run make")
|
||||
self.rebinder = os.path.abspath(self.rebinder)
|
||||
|
||||
self.target_host = "127.0.0.1" # Loopback
|
||||
# Find a free high port
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.bind(('', 0))
|
||||
self.target_port = sock.getsockname()[1]
|
||||
sock.close()
|
||||
|
||||
os.environ.update({
|
||||
"LD_PRELOAD": self.rebinder,
|
||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||
"REBIND_NEW_PORT": str(self.target_port)})
|
||||
|
||||
if self.target_cfg:
|
||||
self.target_cfg = os.path.abspath(self.target_cfg)
|
||||
|
||||
websocket.WebSocketServer.__init__(self, *args, **kwargs)
|
||||
|
||||
def run_wrap_cmd(self):
|
||||
print("Starting '%s'" % " ".join(self.wrap_cmd))
|
||||
self.wrap_times.append(time.time())
|
||||
self.wrap_times.pop(0)
|
||||
self.cmd = subprocess.Popen(
|
||||
self.wrap_cmd, env=os.environ)
|
||||
self.spawn_message = True
|
||||
|
||||
def started(self):
|
||||
"""
|
||||
Called after Websockets server startup (i.e. after daemonize)
|
||||
"""
|
||||
# Need to call wrapped command after daemonization so we can
|
||||
# know when the wrapped command exits
|
||||
if self.wrap_cmd:
|
||||
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
|
||||
elif self.unix_target:
|
||||
dst_string = self.unix_target
|
||||
else:
|
||||
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
||||
|
||||
if self.target_cfg:
|
||||
msg = " - proxying from %s:%s to targets in %s" % (
|
||||
self.listen_host, self.listen_port, self.target_cfg)
|
||||
else:
|
||||
msg = " - proxying from %s:%s to %s" % (
|
||||
self.listen_host, self.listen_port, dst_string)
|
||||
|
||||
if self.ssl_target:
|
||||
msg += " (using SSL)"
|
||||
|
||||
print(msg + "\n")
|
||||
|
||||
if self.wrap_cmd:
|
||||
self.run_wrap_cmd()
|
||||
|
||||
def poll(self):
|
||||
# If we are wrapping a command, check it's status
|
||||
|
||||
if self.wrap_cmd and self.cmd:
|
||||
ret = self.cmd.poll()
|
||||
if ret != None:
|
||||
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
|
||||
self.cmd = None
|
||||
|
||||
if self.wrap_cmd and self.cmd == None:
|
||||
# Response to wrapped command being gone
|
||||
if self.wrap_mode == "ignore":
|
||||
pass
|
||||
elif self.wrap_mode == "exit":
|
||||
sys.exit(ret)
|
||||
elif self.wrap_mode == "respawn":
|
||||
now = time.time()
|
||||
avg = sum(self.wrap_times)/len(self.wrap_times)
|
||||
if (now - avg) < 10:
|
||||
# 3 times in the last 10 seconds
|
||||
if self.spawn_message:
|
||||
print("Command respawning too fast")
|
||||
self.spawn_message = False
|
||||
else:
|
||||
self.run_wrap_cmd()
|
||||
|
||||
#
|
||||
# Routines above this point are run in the master listener
|
||||
# process.
|
||||
#
|
||||
|
||||
#
|
||||
# Routines below this point are connection handler routines and
|
||||
# will be run in a separate forked process for each connection.
|
||||
#
|
||||
|
||||
def new_client(self):
|
||||
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.target_cfg:
|
||||
(self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path)
|
||||
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.wrap_cmd:
|
||||
msg = "connecting to command: %s" % (" ".join(self.wrap_cmd), self.target_port)
|
||||
elif self.unix_target:
|
||||
msg = "connecting to unix socket: %s" % self.unix_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.target_host, self.target_port)
|
||||
|
||||
if self.ssl_target:
|
||||
self.server.target_host, self.server.target_port)
|
||||
|
||||
if self.server.ssl_target:
|
||||
msg += " (using SSL)"
|
||||
self.msg(msg)
|
||||
self.log_message(msg)
|
||||
|
||||
tsock = self.socket(self.target_host, self.target_port,
|
||||
connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target)
|
||||
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)
|
||||
|
||||
if self.verbose and not self.daemon:
|
||||
print(self.traffic_legend)
|
||||
self.print_traffic(self.traffic_legend)
|
||||
|
||||
# Start proxying
|
||||
try:
|
||||
@@ -189,8 +73,9 @@ Traffic Legend:
|
||||
if tsock:
|
||||
tsock.shutdown(socket.SHUT_RDWR)
|
||||
tsock.close()
|
||||
self.vmsg("%s:%s: Closed target" %(
|
||||
self.target_host, self.target_port))
|
||||
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):
|
||||
@@ -205,7 +90,7 @@ Traffic Legend:
|
||||
# Extract the token parameter from url
|
||||
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
||||
|
||||
if not len(args['token']):
|
||||
if not args.has_key('token') or not len(args['token']):
|
||||
raise self.EClose("Token not present")
|
||||
|
||||
token = args['token'][0].rstrip('\n')
|
||||
@@ -239,66 +124,203 @@ Traffic Legend:
|
||||
cqueue = []
|
||||
c_pend = 0
|
||||
tqueue = []
|
||||
rlist = [self.client, target]
|
||||
rlist = [self.request, target]
|
||||
|
||||
while True:
|
||||
wlist = []
|
||||
|
||||
if tqueue: wlist.append(target)
|
||||
if cqueue or c_pend: wlist.append(self.client)
|
||||
if cqueue or c_pend: wlist.append(self.request)
|
||||
ins, outs, excepts = select(rlist, wlist, [], 1)
|
||||
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:
|
||||
self.vmsg("%s:%s: Target closed connection" %(
|
||||
self.target_host, self.target_port))
|
||||
raise self.CClose(1000, "Target closed")
|
||||
|
||||
cqueue.append(buf)
|
||||
self.traffic("{")
|
||||
|
||||
|
||||
if self.client in outs:
|
||||
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 queue for target
|
||||
bufs, closed = self.recv_frames()
|
||||
tqueue.extend(bufs)
|
||||
|
||||
if closed:
|
||||
# TODO: What about blocking on client socket?
|
||||
self.vmsg("%s:%s: Client closed connection" %(
|
||||
self.target_host, self.target_port))
|
||||
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 and per frame traffic")
|
||||
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",
|
||||
@@ -308,6 +330,9 @@ def websockify_init():
|
||||
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,
|
||||
@@ -332,10 +357,15 @@ def websockify_init():
|
||||
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:
|
||||
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:]
|
||||
@@ -346,7 +376,7 @@ def websockify_init():
|
||||
|
||||
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)
|
||||
|
||||
@@ -372,9 +402,70 @@ def websockify_init():
|
||||
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
|
||||
server = WebSocketProxy(**opts.__dict__)
|
||||
server.start_server()
|
||||
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