Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4ae3230f8 |
@@ -4,7 +4,6 @@
|
|||||||
other/.lein-deps-sum
|
other/.lein-deps-sum
|
||||||
other/classes
|
other/classes
|
||||||
other/lib
|
other/lib
|
||||||
other/node_modules
|
|
||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
||||||
target.cfg
|
target.cfg
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
[submodule "include/web-socket-js-project"]
|
|
||||||
path = include/web-socket-js-project
|
|
||||||
url = https://github.com/gimite/web-socket-js.git
|
|
||||||
-50
@@ -1,56 +1,6 @@
|
|||||||
Changes
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
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
|
0.2.0 - Sep 17, 2012
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|||||||
+3
-8
@@ -1,16 +1,11 @@
|
|||||||
websockify is licensed under the LGPL version 3 (see docs/LICENSE.GPL-3 and
|
websockify is licensed under the LGPL version 3 (see docs/LICENSE.GPL-3 and
|
||||||
docs/LICENSE.LGPL-3) with the following exceptions:
|
docs/LICENSE.LGPL-3) with the following exceptions:
|
||||||
|
|
||||||
include/websock.js : MPL 2.0
|
include/base64.js : Choice of MIT 1.1, GPL-2 or LGPL-2.1
|
||||||
|
|
||||||
include/base64.js : MPL 2.0
|
include/web-socket-js/ : New BSD license. Source code at
|
||||||
|
|
||||||
include/des.js : Various BSD style licenses
|
|
||||||
|
|
||||||
include/web-socket-js/ : New BSD license (3-clause). Source code at
|
|
||||||
https://github.com/gimite/web-socket-js
|
https://github.com/gimite/web-socket-js
|
||||||
|
|
||||||
other/kumina.c : Simplified BSD license (2 clause).
|
other/kumina.c : Simplified BSD license (2 clause).
|
||||||
Original source at
|
Original source at
|
||||||
https://github.com/kumina/wsproxy
|
https://github.com/kumina/wsproxy
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -1,2 +1 @@
|
|||||||
include CHANGES.txt README.md LICENSE.txt
|
include CHANGES.txt *.py README.md LICENSE.txt
|
||||||
graft include
|
|
||||||
|
|||||||
@@ -8,42 +8,14 @@ to normal socket traffic. Websockify accepts the WebSockets handshake,
|
|||||||
parses it, and then begins forwarding traffic between the client and
|
parses it, and then begins forwarding traffic between the client and
|
||||||
the target in both directions.
|
the target in both directions.
|
||||||
|
|
||||||
### News/help/contact
|
|
||||||
|
|
||||||
Notable commits, announcements and news are posted to
|
|
||||||
<a href="http://www.twitter.com/noVNC">@noVNC</a>
|
|
||||||
|
|
||||||
If you are a websockify developer/integrator/user (or want to be)
|
|
||||||
please join the <a
|
|
||||||
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC/websockify
|
|
||||||
discussion group</a>
|
|
||||||
|
|
||||||
Bugs and feature requests can be submitted via [github
|
|
||||||
issues](https://github.com/kanaka/websockify/issues).
|
|
||||||
|
|
||||||
If you want to show appreciation for websockify you could donate to a great
|
|
||||||
non-profits such as: [Compassion
|
|
||||||
International](http://www.compassion.com/), [SIL](http://www.sil.org),
|
|
||||||
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
|
|
||||||
Foundation](https://www.eff.org/), [Against Malaria
|
|
||||||
Foundation](http://www.againstmalaria.com/), [Nothing But
|
|
||||||
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
|
|
||||||
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
|
|
||||||
|
|
||||||
### WebSockets binary data
|
### WebSockets binary data
|
||||||
|
|
||||||
Starting with websockify 0.5.0, only the HyBi / IETF
|
Websockify supports all versions of the WebSockets protocol (Hixie and
|
||||||
6455 WebSocket protocol is supported.
|
HyBI). The older Hixie versions of the protocol only support UTF-8
|
||||||
|
text payloads. In order to transport binary data over UTF-8 an
|
||||||
Websockify negotiates whether to base64 encode traffic to and from the
|
encoding must used to encapsulate the data within UTF-8. Websockify
|
||||||
client via the subprotocol header (Sec-WebSocket-Protocol). The valid
|
uses base64 to encode all traffic to and from the client. This does
|
||||||
subprotocol values are 'binary' and 'base64' and if the client sends
|
not affect the data between websockify and the server.
|
||||||
both then the server (the python implementation) will prefer 'binary'.
|
|
||||||
The 'binary' subprotocol indicates that the data will be sent raw
|
|
||||||
using binary WebSocket frames. Some HyBi clients (such as the Flash
|
|
||||||
fallback and older Chrome and iOS versions) do not support binary data
|
|
||||||
which is why the negotiation is necessary.
|
|
||||||
|
|
||||||
|
|
||||||
### Encrypted WebSocket connections (wss://)
|
### Encrypted WebSocket connections (wss://)
|
||||||
|
|
||||||
@@ -138,7 +110,7 @@ new (moved) port of the program.
|
|||||||
The program wrap mode is invoked by replacing the target with `--`
|
The program wrap mode is invoked by replacing the target with `--`
|
||||||
followed by the program command line to wrap.
|
followed by the program command line to wrap.
|
||||||
|
|
||||||
`./run 2023 -- PROGRAM ARGS`
|
`./websockify 2023 -- PROGRAM ARGS`
|
||||||
|
|
||||||
The `--wrap-mode` option can be used to indicate what action to take
|
The `--wrap-mode` option can be used to indicate what action to take
|
||||||
when the wrapped program exits or daemonizes.
|
when the wrapped program exits or daemonizes.
|
||||||
@@ -147,17 +119,16 @@ Here is an example of using websockify to wrap the vncserver command
|
|||||||
(which backgrounds itself) for use with
|
(which backgrounds itself) for use with
|
||||||
[noVNC](https://github.com/kanaka/noVNC):
|
[noVNC](https://github.com/kanaka/noVNC):
|
||||||
|
|
||||||
`./run 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
||||||
|
|
||||||
Here is an example of wrapping telnetd (from krb5-telnetd). telnetd
|
Here is an example of wrapping telnetd (from krb5-telnetd).telnetd
|
||||||
exits after the connection closes so the wrap mode is set to respawn
|
exits after the connection closes so the wrap mode is set to respawn
|
||||||
the command:
|
the command:
|
||||||
|
|
||||||
`sudo ./run 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
||||||
|
|
||||||
The `wstelnet.html` page demonstrates a simple WebSockets based telnet
|
The `wstelnet.html` page demonstrates a simple WebSockets based telnet
|
||||||
client (use 'localhost' and '2023' for the host and port
|
client.
|
||||||
respectively).
|
|
||||||
|
|
||||||
|
|
||||||
### Building the Python ssl module (for python 2.5 and older)
|
### Building the Python ssl module (for python 2.5 and older)
|
||||||
@@ -166,10 +137,11 @@ respectively).
|
|||||||
|
|
||||||
`sudo aptitude install python-dev bluetooth-dev`
|
`sudo aptitude install python-dev bluetooth-dev`
|
||||||
|
|
||||||
* At the top level of the websockify repostory, download, build and
|
* Download, build the ssl module and symlink to it:
|
||||||
symlink the ssl module:
|
|
||||||
|
|
||||||
`wget --no-check-certificate http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
|
`cd websockify/`
|
||||||
|
|
||||||
|
`wget http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
|
||||||
|
|
||||||
`tar xvzf ssl-1.15.tar.gz`
|
`tar xvzf ssl-1.15.tar.gz`
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Installation
|
|||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
|
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
|
||||||
https://github.com/kanaka/websockify/wiki/Compiling-Websockify-as-Windows-Executable
|
https://github.com/kanaka/noVNC/wiki/Compiling-Websockify-to-Windows-Executable
|
||||||
|
|
||||||
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
|
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
|
||||||
|
|
||||||
|
|||||||
@@ -1,373 +0,0 @@
|
|||||||
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,6 +1,9 @@
|
|||||||
- Go implementation
|
- Go implementation
|
||||||
- Rust implementation
|
|
||||||
- Add sub-protocol support to upstream einaros/ws module and use that
|
- Support multiple targets that are selected by the path line. Some
|
||||||
instead of the patched module.
|
sort of wildcarding of ports too.
|
||||||
|
|
||||||
|
- Support SSL targets too.
|
||||||
|
|
||||||
- wstelnet: support CSI L and CSI M
|
- wstelnet: support CSI L and CSI M
|
||||||
|
|
||||||
|
|||||||
+2
-9
@@ -13,12 +13,5 @@ browser.
|
|||||||
|
|
||||||
Building web-socket-js emulator:
|
Building web-socket-js emulator:
|
||||||
|
|
||||||
cd include/web-socket-js/flash-src
|
cd include/web-socket-js/flash-src
|
||||||
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as
|
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as
|
||||||
|
|
||||||
Building release tarball:
|
|
||||||
- not really necessary since tagged revision can be downloaded
|
|
||||||
from github as tarballs
|
|
||||||
|
|
||||||
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar
|
|
||||||
gzip websockify-${WVER}.tar
|
|
||||||
|
|||||||
+4
-8
@@ -1,13 +1,9 @@
|
|||||||
- Update setup.py, CHANGES.txt and other/package.json and commit
|
- Update setup.py and CHANGES.txt and commit
|
||||||
- Create version tag and tarball from tag
|
- Create version tag and tarball from tag
|
||||||
WVER=0.1.0
|
WVER=0.1.0
|
||||||
git tag v${WVER}
|
git tag v${WVER}
|
||||||
git push origin master
|
git push origin master
|
||||||
git push origin v${WVER}
|
git push origin v${WVER}
|
||||||
- Register with pypi.python.org (once):
|
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar
|
||||||
python setup.py register
|
gzip websockify-${WVER}.tar
|
||||||
- Upload the source distribution to pypi
|
- Upload tarball to repo
|
||||||
python setup.py sdist upload
|
|
||||||
- Register with npmjs.org (once)
|
|
||||||
- Upload websockify.js npmjs.org package
|
|
||||||
npm publish other/js
|
|
||||||
|
|||||||
+65
-32
@@ -1,8 +1,45 @@
|
|||||||
/* 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
|
* Modified from:
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
|
||||||
|
*/
|
||||||
|
|
||||||
// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
|
/* ***** 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 ***** */
|
||||||
|
|
||||||
/*jslint white: false, bitwise: false, plusplus: false */
|
/*jslint white: false, bitwise: false, plusplus: false */
|
||||||
/*global console */
|
/*global console */
|
||||||
@@ -10,37 +47,35 @@
|
|||||||
var Base64 = {
|
var Base64 = {
|
||||||
|
|
||||||
/* Convert data (an array of integers) to a Base64 string. */
|
/* Convert data (an array of integers) to a Base64 string. */
|
||||||
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''),
|
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
|
||||||
base64Pad : '=',
|
base64Pad : '=',
|
||||||
|
|
||||||
encode: function (data) {
|
encode: function (data) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var result = '';
|
var result = '',
|
||||||
var toBase64Table = Base64.toBase64Table;
|
chrTable = Base64.toBase64Table.split(''),
|
||||||
var base64Pad = Base64.base64Pad;
|
pad = Base64.base64Pad,
|
||||||
var length = data.length;
|
length = data.length,
|
||||||
var i;
|
i;
|
||||||
// Convert every three bytes to 4 ascii characters.
|
// Convert every three bytes to 4 ascii characters.
|
||||||
/* BEGIN LOOP */
|
|
||||||
for (i = 0; i < (length - 2); i += 3) {
|
for (i = 0; i < (length - 2); i += 3) {
|
||||||
result += toBase64Table[data[i] >> 2];
|
result += chrTable[data[i] >> 2];
|
||||||
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||||
result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
|
result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
|
||||||
result += toBase64Table[data[i+2] & 0x3f];
|
result += chrTable[data[i+2] & 0x3f];
|
||||||
}
|
}
|
||||||
/* END LOOP */
|
|
||||||
|
|
||||||
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
|
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
|
||||||
if (length%3) {
|
if (length%3) {
|
||||||
i = length - (length%3);
|
i = length - (length%3);
|
||||||
result += toBase64Table[data[i] >> 2];
|
result += chrTable[data[i] >> 2];
|
||||||
if ((length%3) === 2) {
|
if ((length%3) === 2) {
|
||||||
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||||
result += toBase64Table[(data[i+1] & 0x0f) << 2];
|
result += chrTable[(data[i+1] & 0x0f) << 2];
|
||||||
result += base64Pad;
|
result += pad;
|
||||||
} else {
|
} else {
|
||||||
result += toBase64Table[(data[i] & 0x03) << 4];
|
result += chrTable[(data[i] & 0x03) << 4];
|
||||||
result += base64Pad + base64Pad;
|
result += pad + pad;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +97,12 @@ toBinaryTable : [
|
|||||||
decode: function (data, offset) {
|
decode: function (data, offset) {
|
||||||
"use strict";
|
"use strict";
|
||||||
offset = typeof(offset) !== 'undefined' ? offset : 0;
|
offset = typeof(offset) !== 'undefined' ? offset : 0;
|
||||||
var toBinaryTable = Base64.toBinaryTable;
|
var binTable = Base64.toBinaryTable,
|
||||||
var base64Pad = Base64.base64Pad;
|
pad = Base64.base64Pad,
|
||||||
var result, result_length, idx, i, c, padding;
|
result, result_length, idx, i, c, padding,
|
||||||
var leftbits = 0; // number of bits decoded, but yet to be appended
|
leftbits = 0, // number of bits decoded, but yet to be appended
|
||||||
var leftdata = 0; // bits decoded, but yet to be appended
|
leftdata = 0, // bits decoded, but yet to be appended
|
||||||
var data_length = data.indexOf('=') - offset;
|
data_length = data.indexOf('=') - offset;
|
||||||
|
|
||||||
if (data_length < 0) { data_length = data.length - offset; }
|
if (data_length < 0) { data_length = data.length - offset; }
|
||||||
|
|
||||||
@@ -76,10 +111,9 @@ decode: function (data, offset) {
|
|||||||
result = new Array(result_length);
|
result = new Array(result_length);
|
||||||
|
|
||||||
// Convert one by one.
|
// Convert one by one.
|
||||||
/* BEGIN LOOP */
|
|
||||||
for (idx = 0, i = offset; i < data.length; i++) {
|
for (idx = 0, i = offset; i < data.length; i++) {
|
||||||
c = toBinaryTable[data.charCodeAt(i) & 0x7f];
|
c = binTable[data.charCodeAt(i) & 0x7f];
|
||||||
padding = (data.charAt(i) === base64Pad);
|
padding = (data.charAt(i) === pad);
|
||||||
// Skip illegal characters and whitespace
|
// Skip illegal characters and whitespace
|
||||||
if (c === -1) {
|
if (c === -1) {
|
||||||
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
|
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
|
||||||
@@ -100,7 +134,6 @@ decode: function (data, offset) {
|
|||||||
leftdata &= (1 << leftbits) - 1;
|
leftdata &= (1 << leftbits) - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* END LOOP */
|
|
||||||
|
|
||||||
// If there are any bits left, the base64 string was corrupted
|
// If there are any bits left, the base64 string was corrupted
|
||||||
if (leftbits) {
|
if (leftbits) {
|
||||||
|
|||||||
+3
-79
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* from noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2011 Joel Martin
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
*/
|
*/
|
||||||
@@ -57,21 +57,6 @@ if (!Array.prototype.map)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// requestAnimationFrame shim with setTimeout fallback
|
|
||||||
//
|
|
||||||
|
|
||||||
window.requestAnimFrame = (function(){
|
|
||||||
return window.requestAnimationFrame ||
|
|
||||||
window.webkitRequestAnimationFrame ||
|
|
||||||
window.mozRequestAnimationFrame ||
|
|
||||||
window.oRequestAnimationFrame ||
|
|
||||||
window.msRequestAnimationFrame ||
|
|
||||||
function(callback){
|
|
||||||
window.setTimeout(callback, 1000 / 60);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ------------------------------------------------------
|
* ------------------------------------------------------
|
||||||
* Namespaced in Util
|
* Namespaced in Util
|
||||||
@@ -146,8 +131,6 @@ Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
|
|||||||
}
|
}
|
||||||
} else if (type in {'integer':1, 'int':1}) {
|
} else if (type in {'integer':1, 'int':1}) {
|
||||||
val = parseInt(val, 10);
|
val = parseInt(val, 10);
|
||||||
} else if (type === 'str') {
|
|
||||||
val = String(val);
|
|
||||||
} else if (type === 'func') {
|
} else if (type === 'func') {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
val = function () {};
|
val = function () {};
|
||||||
@@ -207,65 +190,6 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
|
|||||||
* Cross-browser routines
|
* Cross-browser routines
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// Dynamically load scripts without using document.write()
|
|
||||||
// Reference: http://unixpapa.com/js/dyna.html
|
|
||||||
//
|
|
||||||
// Handles the case where load_scripts is invoked from a script that
|
|
||||||
// itself is loaded via load_scripts. Once all scripts are loaded the
|
|
||||||
// window.onscriptsloaded handler is called (if set).
|
|
||||||
Util.get_include_uri = function() {
|
|
||||||
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
|
|
||||||
}
|
|
||||||
Util._loading_scripts = [];
|
|
||||||
Util._pending_scripts = [];
|
|
||||||
Util.load_scripts = function(files) {
|
|
||||||
var head = document.getElementsByTagName('head')[0], script,
|
|
||||||
ls = Util._loading_scripts, ps = Util._pending_scripts;
|
|
||||||
for (var f=0; f<files.length; f++) {
|
|
||||||
script = document.createElement('script');
|
|
||||||
script.type = 'text/javascript';
|
|
||||||
script.src = Util.get_include_uri() + files[f];
|
|
||||||
//console.log("loading script: " + script.src);
|
|
||||||
script.onload = script.onreadystatechange = function (e) {
|
|
||||||
while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
|
|
||||||
ls[0].readyState === 'complete')) {
|
|
||||||
// For IE, append the script to trigger execution
|
|
||||||
var s = ls.shift();
|
|
||||||
//console.log("loaded script: " + s.src);
|
|
||||||
head.appendChild(s);
|
|
||||||
}
|
|
||||||
if (!this.readyState ||
|
|
||||||
(Util.Engine.presto && this.readyState === 'loaded') ||
|
|
||||||
this.readyState === 'complete') {
|
|
||||||
if (ps.indexOf(this) >= 0) {
|
|
||||||
this.onload = this.onreadystatechange = null;
|
|
||||||
//console.log("completed script: " + this.src);
|
|
||||||
ps.splice(ps.indexOf(this), 1);
|
|
||||||
|
|
||||||
// Call window.onscriptsload after last script loads
|
|
||||||
if (ps.length === 0 && window.onscriptsload) {
|
|
||||||
window.onscriptsload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// In-order script execution tricks
|
|
||||||
if (Util.Engine.trident) {
|
|
||||||
// For IE wait until readyState is 'loaded' before
|
|
||||||
// appending it which will trigger execution
|
|
||||||
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
|
|
||||||
ls.push(script);
|
|
||||||
} else {
|
|
||||||
// For webkit and firefox set async=false and append now
|
|
||||||
// https://developer.mozilla.org/en-US/docs/HTML/Element/script
|
|
||||||
script.async = false;
|
|
||||||
head.appendChild(script);
|
|
||||||
}
|
|
||||||
ps.push(script);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get DOM element position on page
|
// Get DOM element position on page
|
||||||
Util.getPosition = function (obj) {
|
Util.getPosition = function (obj) {
|
||||||
var x = 0, y = 0;
|
var x = 0, y = 0;
|
||||||
|
|||||||
Submodule include/web-socket-js-project deleted from c0855c6cae
Binary file not shown.
@@ -1,69 +1,49 @@
|
|||||||
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
||||||
// License: New BSD License
|
// License: New BSD License
|
||||||
// Reference: http://dev.w3.org/html5/websockets/
|
// Reference: http://dev.w3.org/html5/websockets/
|
||||||
// Reference: http://tools.ietf.org/html/rfc6455
|
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
if (window.WEB_SOCKET_FORCE_FLASH) {
|
if (window.WebSocket) return;
|
||||||
// Keeps going.
|
|
||||||
} else if (window.WebSocket) {
|
var console = window.console;
|
||||||
return;
|
if (!console || !console.log || !console.error) {
|
||||||
} else if (window.MozWebSocket) {
|
console = {log: function(){ }, error: function(){ }};
|
||||||
// Firefox.
|
|
||||||
window.WebSocket = MozWebSocket;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var logger;
|
if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
|
||||||
if (window.WEB_SOCKET_LOGGER) {
|
console.error("Flash Player >= 10.0.0 is required.");
|
||||||
logger = WEB_SOCKET_LOGGER;
|
|
||||||
} else if (window.console && window.console.log && window.console.error) {
|
|
||||||
// In some environment, console is defined but console.log or console.error is missing.
|
|
||||||
logger = window.console;
|
|
||||||
} else {
|
|
||||||
logger = {log: function(){ }, error: function(){ }};
|
|
||||||
}
|
|
||||||
|
|
||||||
// swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
|
|
||||||
if (swfobject.getFlashPlayerVersion().major < 10) {
|
|
||||||
logger.error("Flash Player >= 10.0.0 is required.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (location.protocol == "file:") {
|
if (location.protocol == "file:") {
|
||||||
logger.error(
|
console.error(
|
||||||
"WARNING: web-socket-js doesn't work in file:///... URL " +
|
"WARNING: web-socket-js doesn't work in file:///... URL " +
|
||||||
"unless you set Flash Security Settings properly. " +
|
"unless you set Flash Security Settings properly. " +
|
||||||
"Open the page via Web server i.e. http://...");
|
"Open the page via Web server i.e. http://...");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our own implementation of WebSocket class using Flash.
|
* This class represents a faux web socket.
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @param {array or string} protocols
|
* @param {string} protocol
|
||||||
* @param {string} proxyHost
|
* @param {string} proxyHost
|
||||||
* @param {int} proxyPort
|
* @param {int} proxyPort
|
||||||
* @param {string} headers
|
* @param {string} headers
|
||||||
*/
|
*/
|
||||||
window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
|
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.__id = WebSocket.__nextId++;
|
self.__id = WebSocket.__nextId++;
|
||||||
WebSocket.__instances[self.__id] = self;
|
WebSocket.__instances[self.__id] = self;
|
||||||
self.readyState = WebSocket.CONNECTING;
|
self.readyState = WebSocket.CONNECTING;
|
||||||
self.bufferedAmount = 0;
|
self.bufferedAmount = 0;
|
||||||
self.__events = {};
|
self.__events = {};
|
||||||
if (!protocols) {
|
|
||||||
protocols = [];
|
|
||||||
} else if (typeof protocols == "string") {
|
|
||||||
protocols = [protocols];
|
|
||||||
}
|
|
||||||
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
|
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
|
||||||
// Otherwise, when onopen fires immediately, onopen is called before it is set.
|
// Otherwise, when onopen fires immediately, onopen is called before it is set.
|
||||||
self.__createTask = setTimeout(function() {
|
setTimeout(function() {
|
||||||
WebSocket.__addTask(function() {
|
WebSocket.__addTask(function() {
|
||||||
self.__createTask = null;
|
|
||||||
WebSocket.__flash.create(
|
WebSocket.__flash.create(
|
||||||
self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
|
self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null);
|
||||||
});
|
});
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
@@ -98,12 +78,6 @@
|
|||||||
* Close this web socket gracefully.
|
* Close this web socket gracefully.
|
||||||
*/
|
*/
|
||||||
WebSocket.prototype.close = function() {
|
WebSocket.prototype.close = function() {
|
||||||
if (this.__createTask) {
|
|
||||||
clearTimeout(this.__createTask);
|
|
||||||
this.__createTask = null;
|
|
||||||
this.readyState = WebSocket.CLOSED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
|
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -157,7 +131,7 @@
|
|||||||
events[i](event);
|
events[i](event);
|
||||||
}
|
}
|
||||||
var handler = this["on" + event.type];
|
var handler = this["on" + event.type];
|
||||||
if (handler) handler.apply(this, [event]);
|
if (handler) handler(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,22 +139,16 @@
|
|||||||
* @param {Object} flashEvent
|
* @param {Object} flashEvent
|
||||||
*/
|
*/
|
||||||
WebSocket.prototype.__handleEvent = function(flashEvent) {
|
WebSocket.prototype.__handleEvent = function(flashEvent) {
|
||||||
|
|
||||||
if ("readyState" in flashEvent) {
|
if ("readyState" in flashEvent) {
|
||||||
this.readyState = flashEvent.readyState;
|
this.readyState = flashEvent.readyState;
|
||||||
}
|
}
|
||||||
if ("protocol" in flashEvent) {
|
|
||||||
this.protocol = flashEvent.protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
var jsEvent;
|
var jsEvent;
|
||||||
if (flashEvent.type == "open" || flashEvent.type == "error") {
|
if (flashEvent.type == "open" || flashEvent.type == "error") {
|
||||||
jsEvent = this.__createSimpleEvent(flashEvent.type);
|
jsEvent = this.__createSimpleEvent(flashEvent.type);
|
||||||
} else if (flashEvent.type == "close") {
|
} else if (flashEvent.type == "close") {
|
||||||
|
// TODO implement jsEvent.wasClean
|
||||||
jsEvent = this.__createSimpleEvent("close");
|
jsEvent = this.__createSimpleEvent("close");
|
||||||
jsEvent.wasClean = flashEvent.wasClean ? true : false;
|
|
||||||
jsEvent.code = flashEvent.code;
|
|
||||||
jsEvent.reason = flashEvent.reason;
|
|
||||||
} else if (flashEvent.type == "message") {
|
} else if (flashEvent.type == "message") {
|
||||||
var data = decodeURIComponent(flashEvent.message);
|
var data = decodeURIComponent(flashEvent.message);
|
||||||
jsEvent = this.__createMessageEvent("message", data);
|
jsEvent = this.__createMessageEvent("message", data);
|
||||||
@@ -189,7 +157,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.dispatchEvent(jsEvent);
|
this.dispatchEvent(jsEvent);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
WebSocket.prototype.__createSimpleEvent = function(type) {
|
WebSocket.prototype.__createSimpleEvent = function(type) {
|
||||||
@@ -221,9 +188,6 @@
|
|||||||
WebSocket.CLOSING = 2;
|
WebSocket.CLOSING = 2;
|
||||||
WebSocket.CLOSED = 3;
|
WebSocket.CLOSED = 3;
|
||||||
|
|
||||||
// Field to check implementation of WebSocket.
|
|
||||||
WebSocket.__isFlashImplementation = true;
|
|
||||||
WebSocket.__initialized = false;
|
|
||||||
WebSocket.__flash = null;
|
WebSocket.__flash = null;
|
||||||
WebSocket.__instances = {};
|
WebSocket.__instances = {};
|
||||||
WebSocket.__tasks = [];
|
WebSocket.__tasks = [];
|
||||||
@@ -243,31 +207,16 @@
|
|||||||
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
|
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
|
||||||
*/
|
*/
|
||||||
WebSocket.__initialize = function() {
|
WebSocket.__initialize = function() {
|
||||||
|
if (WebSocket.__flash) return;
|
||||||
if (WebSocket.__initialized) return;
|
|
||||||
WebSocket.__initialized = true;
|
|
||||||
|
|
||||||
if (WebSocket.__swfLocation) {
|
if (WebSocket.__swfLocation) {
|
||||||
// For backword compatibility.
|
// For backword compatibility.
|
||||||
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
|
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
|
||||||
}
|
}
|
||||||
if (!window.WEB_SOCKET_SWF_LOCATION) {
|
if (!window.WEB_SOCKET_SWF_LOCATION) {
|
||||||
logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
|
|
||||||
!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
|
|
||||||
WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
|
|
||||||
var swfHost = RegExp.$1;
|
|
||||||
if (location.host != swfHost) {
|
|
||||||
logger.error(
|
|
||||||
"[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
|
|
||||||
"('" + location.host + "' != '" + swfHost + "'). " +
|
|
||||||
"See also 'How to host HTML file and SWF file in different domains' section " +
|
|
||||||
"in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
|
|
||||||
"by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var container = document.createElement("div");
|
var container = document.createElement("div");
|
||||||
container.id = "webSocketContainer";
|
container.id = "webSocketContainer";
|
||||||
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
|
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
|
||||||
@@ -301,11 +250,9 @@
|
|||||||
null,
|
null,
|
||||||
function(e) {
|
function(e) {
|
||||||
if (!e.success) {
|
if (!e.success) {
|
||||||
logger.error("[WebSocket] swfobject.embedSWF failed");
|
console.error("[WebSocket] swfobject.embedSWF failed");
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -340,7 +287,7 @@
|
|||||||
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
|
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
return true;
|
return true;
|
||||||
@@ -348,12 +295,12 @@
|
|||||||
|
|
||||||
// Called by Flash.
|
// Called by Flash.
|
||||||
WebSocket.__log = function(message) {
|
WebSocket.__log = function(message) {
|
||||||
logger.log(decodeURIComponent(message));
|
console.log(decodeURIComponent(message));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Called by Flash.
|
// Called by Flash.
|
||||||
WebSocket.__error = function(message) {
|
WebSocket.__error = function(message) {
|
||||||
logger.error(decodeURIComponent(message));
|
console.error(decodeURIComponent(message));
|
||||||
};
|
};
|
||||||
|
|
||||||
WebSocket.__addTask = function(task) {
|
WebSocket.__addTask = function(task) {
|
||||||
@@ -380,12 +327,15 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
|
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
|
||||||
// NOTE:
|
if (window.addEventListener) {
|
||||||
// This fires immediately if web_socket.js is dynamically loaded after
|
window.addEventListener("load", function(){
|
||||||
// the document is loaded.
|
WebSocket.__initialize();
|
||||||
swfobject.addDomLoadEvent(function() {
|
}, false);
|
||||||
WebSocket.__initialize();
|
} else {
|
||||||
});
|
window.attachEvent("onload", function(){
|
||||||
|
WebSocket.__initialize();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
+21
-25
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Websock: high-performance binary WebSockets
|
* Websock: high-performance binary WebSockets
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2012 Joel Martin
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* Websock is similar to the standard WebSocket object but Websock
|
* Websock is similar to the standard WebSocket object but Websock
|
||||||
* enables communication with raw TCP sockets (i.e. the binary stream)
|
* enables communication with raw TCP sockets (i.e. the binary stream)
|
||||||
@@ -35,14 +35,23 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
|||||||
|
|
||||||
Websock_native = false;
|
Websock_native = false;
|
||||||
(function () {
|
(function () {
|
||||||
window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
|
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() +
|
||||||
"web-socket-js/WebSocketMain.swf";
|
"web-socket-js/WebSocketMain.swf";
|
||||||
if (Util.Engine.trident) {
|
if (Util.Engine.trident) {
|
||||||
Util.Debug("Forcing uncached load of WebSocketMain.swf");
|
Util.Debug("Forcing uncached load of WebSocketMain.swf");
|
||||||
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
||||||
}
|
}
|
||||||
Util.load_scripts(["web-socket-js/swfobject.js",
|
extra += start + "web-socket-js/swfobject.js" + end;
|
||||||
"web-socket-js/web_socket.js"]);
|
extra += start + "web-socket-js/web_socket.js" + end;
|
||||||
|
document.write(extra);
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +61,7 @@ function Websock() {
|
|||||||
|
|
||||||
var api = {}, // Public API
|
var api = {}, // Public API
|
||||||
websocket = null, // WebSocket object
|
websocket = null, // WebSocket object
|
||||||
mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
|
mode = 'base64',
|
||||||
rQ = [], // Receive queue
|
rQ = [], // Receive queue
|
||||||
rQi = 0, // Receive queue index
|
rQi = 0, // Receive queue index
|
||||||
rQmax = 10000, // Max receive queue size before compacting
|
rQmax = 10000, // Max receive queue size before compacting
|
||||||
@@ -159,11 +168,10 @@ function rQwait(msg, num, goback) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
function encode_message() {
|
function encode_message() {
|
||||||
|
/* base64 encode */
|
||||||
if (mode === 'binary') {
|
if (mode === 'binary') {
|
||||||
// Put in a binary arraybuffer
|
|
||||||
return (new Uint8Array(sQ)).buffer;
|
return (new Uint8Array(sQ)).buffer;
|
||||||
} else {
|
} else {
|
||||||
// base64 encode
|
|
||||||
return Base64.encode(sQ);
|
return Base64.encode(sQ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,10 +180,7 @@ function decode_message(data) {
|
|||||||
//Util.Debug(">> decode_message: " + data);
|
//Util.Debug(">> decode_message: " + data);
|
||||||
if (mode === 'binary') {
|
if (mode === 'binary') {
|
||||||
// push arraybuffer values onto the end
|
// push arraybuffer values onto the end
|
||||||
var u8 = new Uint8Array(data);
|
rQ.push.apply(rQ, (new Uint8Array(data)));
|
||||||
for (var i = 0; i < u8.length; i++) {
|
|
||||||
rQ.push(u8[i]);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// base64 decode and concat to the end
|
// base64 decode and concat to the end
|
||||||
rQ = rQ.concat(Base64.decode(data, 0));
|
rQ = rQ.concat(Base64.decode(data, 0));
|
||||||
@@ -305,19 +310,11 @@ function init(protocols) {
|
|||||||
throw("WebSocket binary sub-protocol requested but not supported");
|
throw("WebSocket binary sub-protocol requested but not supported");
|
||||||
}
|
}
|
||||||
if (typeof(protocols) === "object") {
|
if (typeof(protocols) === "object") {
|
||||||
var new_protocols = [];
|
|
||||||
for (var i = 0; i < protocols.length; i++) {
|
for (var i = 0; i < protocols.length; i++) {
|
||||||
if (protocols[i] === 'binary') {
|
if (protocols[i] === 'binary') {
|
||||||
Util.Error("Skipping unsupported WebSocket binary sub-protocol");
|
throw("WebSocket binary sub-protocol requested but not supported");
|
||||||
} 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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,9 +328,6 @@ function open(uri, protocols) {
|
|||||||
websocket = {};
|
websocket = {};
|
||||||
} else {
|
} else {
|
||||||
websocket = new WebSocket(uri, protocols);
|
websocket = new WebSocket(uri, protocols);
|
||||||
if (protocols.indexOf('binary') >= 0) {
|
|
||||||
websocket.binaryType = 'arraybuffer';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
websocket.onmessage = recv_message;
|
websocket.onmessage = recv_message;
|
||||||
@@ -346,6 +340,9 @@ function open(uri, protocols) {
|
|||||||
mode = 'base64';
|
mode = 'base64';
|
||||||
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
|
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
|
||||||
}
|
}
|
||||||
|
if (mode === 'binary') {
|
||||||
|
websocket.binaryType = 'arraybuffer';
|
||||||
|
}
|
||||||
eventHandlers.open();
|
eventHandlers.open();
|
||||||
Util.Debug("<< WebSock.onopen");
|
Util.Debug("<< WebSock.onopen");
|
||||||
};
|
};
|
||||||
@@ -374,9 +371,8 @@ function close() {
|
|||||||
|
|
||||||
// Override internal functions for testing
|
// Override internal functions for testing
|
||||||
// Takes a send function, returns reference to recv function
|
// Takes a send function, returns reference to recv function
|
||||||
function testMode(override_send, data_mode) {
|
function testMode(override_send) {
|
||||||
test_mode = true;
|
test_mode = true;
|
||||||
mode = data_mode;
|
|
||||||
api.send = override_send;
|
api.send = override_send;
|
||||||
api.close = function () {};
|
api.close = function () {};
|
||||||
return recv_message;
|
return recv_message;
|
||||||
|
|||||||
+10
-78
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* from noVNC: HTML5 VNC client
|
* from noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2010 Joel Martin
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
*/
|
*/
|
||||||
@@ -37,16 +37,14 @@ if (!window.$D) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// init log level reading the logging HTTP param
|
// init log level reading the logging HTTP param
|
||||||
WebUtil.init_logging = function(level) {
|
WebUtil.init_logging = function() {
|
||||||
if (typeof level !== "undefined") {
|
Util._log_level = (document.location.href.match(
|
||||||
Util._log_level = level;
|
/logging=([A-Za-z0-9\._\-]*)/) ||
|
||||||
} else {
|
['', Util._log_level])[1];
|
||||||
Util._log_level = (document.location.href.match(
|
|
||||||
/logging=([A-Za-z0-9\._\-]*)/) ||
|
|
||||||
['', Util._log_level])[1];
|
|
||||||
}
|
|
||||||
Util.init_logging();
|
Util.init_logging();
|
||||||
};
|
};
|
||||||
|
WebUtil.init_logging();
|
||||||
|
|
||||||
|
|
||||||
WebUtil.dirObj = function (obj, depth, parent) {
|
WebUtil.dirObj = function (obj, depth, parent) {
|
||||||
@@ -77,14 +75,9 @@ WebUtil.dirObj = function (obj, depth, parent) {
|
|||||||
|
|
||||||
// Read a query string variable
|
// Read a query string variable
|
||||||
WebUtil.getQueryVar = function(name, defVal) {
|
WebUtil.getQueryVar = function(name, defVal) {
|
||||||
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'),
|
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)');
|
||||||
match = document.location.href.match(re);
|
|
||||||
if (typeof defVal === 'undefined') { defVal = null; }
|
if (typeof defVal === 'undefined') { defVal = null; }
|
||||||
if (match) {
|
return (document.location.href.match(re) || ['',defVal])[1];
|
||||||
return decodeURIComponent(match[1]);
|
|
||||||
} else {
|
|
||||||
return defVal;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -120,67 +113,6 @@ WebUtil.eraseCookie = function(name) {
|
|||||||
WebUtil.createCookie(name,"",-1);
|
WebUtil.createCookie(name,"",-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* Setting handling.
|
|
||||||
*/
|
|
||||||
|
|
||||||
WebUtil.initSettings = function(callback) {
|
|
||||||
var callbackArgs = Array.prototype.slice.call(arguments, 1);
|
|
||||||
if (window.chrome && window.chrome.storage) {
|
|
||||||
window.chrome.storage.sync.get(function (cfg) {
|
|
||||||
WebUtil.settings = cfg;
|
|
||||||
console.log(WebUtil.settings);
|
|
||||||
if (callback) {
|
|
||||||
callback.apply(this, callbackArgs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// No-op
|
|
||||||
if (callback) {
|
|
||||||
callback.apply(this, callbackArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// No days means only for this browser session
|
|
||||||
WebUtil.writeSetting = function(name, value) {
|
|
||||||
if (window.chrome && window.chrome.storage) {
|
|
||||||
//console.log("writeSetting:", name, value);
|
|
||||||
if (WebUtil.settings[name] !== value) {
|
|
||||||
WebUtil.settings[name] = value;
|
|
||||||
window.chrome.storage.sync.set(WebUtil.settings);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
localStorage.setItem(name, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WebUtil.readSetting = function(name, defaultValue) {
|
|
||||||
var value;
|
|
||||||
if (window.chrome && window.chrome.storage) {
|
|
||||||
value = WebUtil.settings[name];
|
|
||||||
} else {
|
|
||||||
value = localStorage.getItem(name);
|
|
||||||
}
|
|
||||||
if (typeof value === "undefined") {
|
|
||||||
value = null;
|
|
||||||
}
|
|
||||||
if (value === null && typeof defaultValue !== undefined) {
|
|
||||||
return defaultValue;
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WebUtil.eraseSetting = function(name) {
|
|
||||||
if (window.chrome && window.chrome.storage) {
|
|
||||||
window.chrome.storage.sync.remove(name);
|
|
||||||
delete WebUtil.settings[name];
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem(name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Alternate stylesheet selection
|
* Alternate stylesheet selection
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
-14
@@ -16,8 +16,6 @@ usage() {
|
|||||||
echo " Default: localhost:5900"
|
echo " Default: localhost:5900"
|
||||||
echo " --cert CERT Path to combined cert/key file"
|
echo " --cert CERT Path to combined cert/key file"
|
||||||
echo " Default: self.pem"
|
echo " Default: self.pem"
|
||||||
echo " --web WEB Path to web files (e.g. vnc.html)"
|
|
||||||
echo " Default: ./"
|
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +24,6 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
|
|||||||
PORT="6080"
|
PORT="6080"
|
||||||
VNC_DEST="localhost:5900"
|
VNC_DEST="localhost:5900"
|
||||||
CERT=""
|
CERT=""
|
||||||
WEB=""
|
|
||||||
proxy_pid=""
|
proxy_pid=""
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
@@ -53,7 +50,6 @@ while [ "$*" ]; do
|
|||||||
--listen) PORT="${OPTARG}"; shift ;;
|
--listen) PORT="${OPTARG}"; shift ;;
|
||||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||||
--cert) CERT="${OPTARG}"; shift ;;
|
--cert) CERT="${OPTARG}"; shift ;;
|
||||||
--web) WEB="${OPTARG}"; shift ;;
|
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||||
*) break ;;
|
*) break ;;
|
||||||
@@ -64,24 +60,18 @@ done
|
|||||||
which netstat >/dev/null 2>&1 \
|
which netstat >/dev/null 2>&1 \
|
||||||
|| die "Must have netstat installed"
|
|| die "Must have netstat installed"
|
||||||
|
|
||||||
netstat -ltn | grep -qs "${PORT} .*LISTEN" \
|
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
|
||||||
&& die "Port ${PORT} in use. Try --listen PORT"
|
&& die "Port ${PORT} in use. Try --listen PORT"
|
||||||
|
|
||||||
trap "cleanup" TERM QUIT INT EXIT
|
trap "cleanup" TERM QUIT INT EXIT
|
||||||
|
|
||||||
# Find vnc.html
|
# Find vnc.html
|
||||||
if [ -n "${WEB}" ]; then
|
if [ -e "$(pwd)/vnc.html" ]; then
|
||||||
if [ ! -e "${WEB}/vnc.html" ]; then
|
|
||||||
die "Could not find ${WEB}/vnc.html"
|
|
||||||
fi
|
|
||||||
elif [ -e "$(pwd)/vnc.html" ]; then
|
|
||||||
WEB=$(pwd)
|
WEB=$(pwd)
|
||||||
elif [ -e "${HERE}/../vnc.html" ]; then
|
elif [ -e "${HERE}/../vnc.html" ]; then
|
||||||
WEB=${HERE}/../
|
WEB=${HERE}/../
|
||||||
elif [ -e "${HERE}/vnc.html" ]; then
|
elif [ -e "${HERE}/vnc.html" ]; then
|
||||||
WEB=${HERE}
|
WEB=${HERE}
|
||||||
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
|
|
||||||
WEB=${HERE}/../share/novnc/
|
|
||||||
else
|
else
|
||||||
die "Could not find vnc.html"
|
die "Could not find vnc.html"
|
||||||
fi
|
fi
|
||||||
@@ -102,7 +92,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||||
${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
${HERE}/wsproxy.py --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||||
proxy_pid="$!"
|
proxy_pid="$!"
|
||||||
sleep 1
|
sleep 1
|
||||||
if ! ps -p ${proxy_pid} >/dev/null; then
|
if ! ps -p ${proxy_pid} >/dev/null; then
|
||||||
@@ -111,7 +101,7 @@ if ! ps -p ${proxy_pid} >/dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n\nNavigate to this URL:\n"
|
echo -e "\n\nNavigate to to this URL:\n"
|
||||||
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||||
echo -e "Press Ctrl-C to exit\n\n"
|
echo -e "Press Ctrl-C to exit\n\n"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -795,7 +795,7 @@ void start_server() {
|
|||||||
}
|
}
|
||||||
handler_msg("handler exit\n");
|
handler_msg("handler exit\n");
|
||||||
} else {
|
} else {
|
||||||
handler_msg("websockify exit\n");
|
handler_msg("wsproxy exit\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-61
@@ -9,22 +9,11 @@
|
|||||||
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
||||||
|
|
||||||
require 'gserver'
|
require 'gserver'
|
||||||
require 'openssl'
|
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
require 'digest/md5'
|
require 'digest/md5'
|
||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
require 'base64'
|
require 'base64'
|
||||||
|
|
||||||
unless OpenSSL::SSL::SSLSocket.instance_methods.index("read_nonblock")
|
|
||||||
module OpenSSL
|
|
||||||
module SSL
|
|
||||||
class SSLSocket
|
|
||||||
alias :read_nonblock :readpartial
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class EClose < Exception
|
class EClose < Exception
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -55,17 +44,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
host = opts['listen_host'] || GServer::DEFAULT_HOST
|
host = opts['listen_host'] || GServer::DEFAULT_HOST
|
||||||
|
|
||||||
super(port, host)
|
super(port, host)
|
||||||
msg opts.inspect
|
|
||||||
if opts['server_cert']
|
|
||||||
msg "creating ssl context"
|
|
||||||
@sslContext = OpenSSL::SSL::SSLContext.new
|
|
||||||
@sslContext.cert = OpenSSL::X509::Certificate.new(File.open(opts['server_cert']))
|
|
||||||
@sslContext.key = OpenSSL::PKey::RSA.new(File.open(opts['server_key']))
|
|
||||||
@sslContext.ca_file = opts['server_cert']
|
|
||||||
@sslContext.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
||||||
@sslContext.verify_depth = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
@@client_id = 0 # Track client number total on class
|
@@client_id = 0 # Track client number total on class
|
||||||
|
|
||||||
@verbose = opts['verbose']
|
@verbose = opts['verbose']
|
||||||
@@ -74,18 +53,6 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
def serve(io)
|
def serve(io)
|
||||||
@@client_id += 1
|
@@client_id += 1
|
||||||
msg self.inspect
|
|
||||||
if @sslContext
|
|
||||||
msg "Enabling SSL context"
|
|
||||||
ssl = OpenSSL::SSL::SSLSocket.new(io, @sslContext)
|
|
||||||
#ssl.sync_close = true
|
|
||||||
#ssl.sync = true
|
|
||||||
msg "SSL accepting"
|
|
||||||
ssl.accept
|
|
||||||
io = ssl # replace the unencrypted handle with the encrypted one
|
|
||||||
end
|
|
||||||
|
|
||||||
msg "initializing thread"
|
|
||||||
|
|
||||||
# Initialize per thread state
|
# Initialize per thread state
|
||||||
t = Thread.current
|
t = Thread.current
|
||||||
@@ -94,11 +61,11 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
t[:recv_part] = nil
|
t[:recv_part] = nil
|
||||||
t[:base64] = nil
|
t[:base64] = nil
|
||||||
|
|
||||||
puts "in serve, client: #{t[:my_client_id].inspect}"
|
puts "in serve, client: #{t[:client].inspect}"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
t[:client] = do_handshake(io)
|
t[:client] = do_handshake(io)
|
||||||
new_websocket_client(t[:client])
|
new_client(t[:client])
|
||||||
rescue EClose => e
|
rescue EClose => e
|
||||||
msg "Client closed: #{e.message}"
|
msg "Client closed: #{e.message}"
|
||||||
return
|
return
|
||||||
@@ -118,12 +85,12 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
if @verbose then print token; STDOUT.flush; end
|
if @verbose then print token; STDOUT.flush; end
|
||||||
end
|
end
|
||||||
|
|
||||||
def msg(m)
|
def msg(msg)
|
||||||
printf("% 3d: %s\n", Thread.current[:my_client_id] || 0, m)
|
puts "% 3d: %s" % [Thread.current[:my_client_id], msg]
|
||||||
end
|
end
|
||||||
|
|
||||||
def vmsg(m)
|
def vmsg(msg)
|
||||||
if @verbose then msg(m) end
|
if @verbose then msg(msg) end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -143,11 +110,12 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
def unmask(buf, hlen, length)
|
def unmask(buf, hlen, length)
|
||||||
pstart = hlen + 4
|
pstart = hlen + 4
|
||||||
mask = buf[hlen...hlen+4].each_byte.map{|b|b}
|
mask = buf[hlen...hlen+4]
|
||||||
data = buf[pstart...pstart+length]
|
data = buf[pstart...pstart+length]
|
||||||
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
|
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
|
||||||
i=-1
|
for i in (0...data.length) do
|
||||||
data = data.each_byte.map{|b| i+=1; (b ^ mask[i % 4]).chr}.join("")
|
data[i] ^= mask[i%4]
|
||||||
|
end
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -269,7 +237,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
while t[:send_parts].length > 0
|
while t[:send_parts].length > 0
|
||||||
buf = t[:send_parts].shift
|
buf = t[:send_parts].shift
|
||||||
sent = t[:client].write(buf)
|
sent = t[:client].send(buf, 0)
|
||||||
|
|
||||||
if sent == buf.length
|
if sent == buf.length
|
||||||
traffic "<"
|
traffic "<"
|
||||||
@@ -289,7 +257,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
closed = false
|
closed = false
|
||||||
bufs = []
|
bufs = []
|
||||||
|
|
||||||
buf = t[:client].read_nonblock(@@Buffer_size)
|
buf = t[:client].recv(@@Buffer_size)
|
||||||
|
|
||||||
if buf.length == 0
|
if buf.length == 0
|
||||||
return bufs, "Client closed abrubtly"
|
return bufs, "Client closed abrubtly"
|
||||||
@@ -318,10 +286,10 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if buf[0...2] == "\xff\x00"
|
if buf[0...2] == "\xff\x00":
|
||||||
closed = "Client sent orderly close frame"
|
closed = "Client sent orderly close frame"
|
||||||
break
|
break
|
||||||
elsif buf[0...2] == "\x00\xff"
|
elsif buf[0...2] == "\x00\xff":
|
||||||
buf = buf[2...buf.length]
|
buf = buf[2...buf.length]
|
||||||
continue # No-op frame
|
continue # No-op frame
|
||||||
elsif buf.count("\xff") == 0
|
elsif buf.count("\xff") == 0
|
||||||
@@ -340,7 +308,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
bufs << frame['payload']
|
bufs << frame['payload']
|
||||||
|
|
||||||
if frame['left'] > 0
|
if frame['left'] > 0:
|
||||||
buf = buf[-frame['left']...buf.length]
|
buf = buf[-frame['left']...buf.length]
|
||||||
else
|
else
|
||||||
buf = ''
|
buf = ''
|
||||||
@@ -360,10 +328,10 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
end
|
end
|
||||||
|
|
||||||
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
|
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
|
||||||
t[:client].write(buf)
|
t[:client].send(buf, 0)
|
||||||
elsif t[:version] == "hixie-76"
|
elsif t[:version] == "hixie-76"
|
||||||
buf = "\xff\x00"
|
buf = "\xff\x00"
|
||||||
t[:client].write(buf)
|
t[:client].send(buf, 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -376,21 +344,16 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
raise EClose, "ignoring socket not ready"
|
raise EClose, "ignoring socket not ready"
|
||||||
end
|
end
|
||||||
|
|
||||||
handshake = ""
|
handshake = sock.recv(1024, Socket::MSG_PEEK)
|
||||||
msg "About to read from sock [#{sock.inspect}]"
|
#msg "Handshake [#{handshake.inspect}]"
|
||||||
handshake = sock.read_nonblock(1024)
|
|
||||||
msg "Handshake [#{handshake.inspect}]"
|
|
||||||
|
|
||||||
if handshake == nil or handshake == ""
|
if handshake == ""
|
||||||
raise(EClose, "ignoring empty handshake")
|
raise(EClose, "ignoring empty handshake")
|
||||||
else
|
else
|
||||||
stype = "Plain non-SSL (ws://)"
|
stype = "Plain non-SSL (ws://)"
|
||||||
scheme = "ws"
|
scheme = "ws"
|
||||||
if sock.class == OpenSSL::SSL::SSLSocket
|
|
||||||
stype = "SSL (wss://)"
|
|
||||||
scheme = "wss"
|
|
||||||
end
|
|
||||||
retsock = sock
|
retsock = sock
|
||||||
|
sock.recv(1024)
|
||||||
end
|
end
|
||||||
|
|
||||||
h = t[:headers] = {}
|
h = t[:headers] = {}
|
||||||
@@ -402,7 +365,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
|
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
|
||||||
h[hsplit[1].strip.downcase] = hsplit[2]
|
h[hsplit[1].strip.downcase] = hsplit[2]
|
||||||
end
|
end
|
||||||
puts "Headers: #{h.inspect}"
|
#puts "Headers: #{h.inspect}"
|
||||||
|
|
||||||
unless h.has_key?('upgrade') &&
|
unless h.has_key?('upgrade') &&
|
||||||
h['upgrade'].downcase == 'websocket'
|
h['upgrade'].downcase == 'websocket'
|
||||||
@@ -482,7 +445,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
if t[:path] then msg "Path: '%s'" % [t[:path]] end
|
if t[:path] then msg "Path: '%s'" % [t[:path]] end
|
||||||
|
|
||||||
#puts "sending reponse #{response.inspect}"
|
#puts "sending reponse #{response.inspect}"
|
||||||
retsock.write(response)
|
retsock.send(response, 0)
|
||||||
|
|
||||||
# Return the WebSocket socket which may be SSL wrapped
|
# Return the WebSocket socket which may be SSL wrapped
|
||||||
return retsock
|
return retsock
|
||||||
|
|||||||
Executable → Regular
+24
-76
@@ -1,82 +1,53 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// A WebSocket to TCP socket proxy
|
// A WebSocket to TCP socket proxy
|
||||||
// Copyright 2012 Joel Martin
|
// Copyright 2012 Joel Martin
|
||||||
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||||
|
|
||||||
// Known to work with node 0.8.9
|
// Known to work with node 0.6
|
||||||
// Requires node modules: ws, optimist and policyfile
|
// Requires node modules: ws, base64, optimist and policyfile
|
||||||
// npm install ws optimist policyfile
|
// npm install ws base64 optimist policyfile
|
||||||
|
|
||||||
|
|
||||||
var argv = require('optimist').argv,
|
var argv = require('optimist').argv,
|
||||||
net = require('net'),
|
net = require('net'),
|
||||||
http = require('http'),
|
http = require('http'),
|
||||||
https = require('https'),
|
|
||||||
url = require('url'),
|
url = require('url'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
fs = require('fs'),
|
fs = require('fs'),
|
||||||
policyfile = require('policyfile'),
|
policyfile = require('policyfile'),
|
||||||
|
|
||||||
|
base64 = require('base64/build/Release/base64'),
|
||||||
Buffer = require('buffer').Buffer,
|
Buffer = require('buffer').Buffer,
|
||||||
WebSocketServer = require('ws').Server,
|
WebSocketServer = require('ws').Server,
|
||||||
|
|
||||||
webServer, wsServer,
|
httpServer, wsServer,
|
||||||
source_host, source_port, target_host, target_port,
|
source_host, source_port, target_host, target_port,
|
||||||
web_path = null;
|
web_path = null;
|
||||||
|
|
||||||
|
|
||||||
// Handle new WebSocket client
|
// Handle new WebSocket client
|
||||||
new_client = function(client) {
|
new_client = function(client) {
|
||||||
var clientAddr = client._socket.remoteAddress, log;
|
console.log('WebSocket client connected');
|
||||||
console.log(client.upgradeReq.url);
|
//console.log('protocol: ' + client.protocol);
|
||||||
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, function() {
|
var target = net.createConnection(target_port,target_host);
|
||||||
log('connected to target');
|
target.on('begin', function() {
|
||||||
|
console.log('connected to target');
|
||||||
});
|
});
|
||||||
target.on('data', function(data) {
|
target.on('data', function(data) {
|
||||||
//log("sending message: " + data);
|
client.send(base64.encode(new Buffer(data)));
|
||||||
try {
|
|
||||||
if (client.protocol === 'base64') {
|
|
||||||
client.send(new Buffer(data).toString('base64'));
|
|
||||||
} else {
|
|
||||||
client.send(data,{binary: true});
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
log("Client closed, cleaning up target");
|
|
||||||
target.end();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
target.on('end', function() {
|
target.on('end', function() {
|
||||||
log('target disconnected');
|
console.log('target disconnected');
|
||||||
client.close();
|
|
||||||
});
|
|
||||||
target.on('error', function() {
|
|
||||||
log('target connection error');
|
|
||||||
target.end();
|
|
||||||
client.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('message', function(msg) {
|
client.on('message', function(msg) {
|
||||||
//log('got message: ' + msg);
|
//console.log('got some message');
|
||||||
if (client.protocol === 'base64') {
|
target.write(base64.decode(msg),'binary');
|
||||||
target.write(new Buffer(msg, 'base64'));
|
|
||||||
} else {
|
|
||||||
target.write(msg,'binary');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
client.on('close', function(code, reason) {
|
client.on('close', function(code, reason) {
|
||||||
log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
|
console.log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
|
||||||
target.end();
|
|
||||||
});
|
});
|
||||||
client.on('error', function(a) {
|
client.on('error', function(a) {
|
||||||
log('WebSocket client error: ' + a);
|
console.log('WebSocket client error: ' + a);
|
||||||
target.end();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,7 +73,7 @@ http_request = function (request, response) {
|
|||||||
var uri = url.parse(request.url).pathname
|
var uri = url.parse(request.url).pathname
|
||||||
, filename = path.join(argv.web, uri);
|
, filename = path.join(argv.web, uri);
|
||||||
|
|
||||||
fs.exists(filename, function(exists) {
|
path.exists(filename, function(exists) {
|
||||||
if(!exists) {
|
if(!exists) {
|
||||||
return http_error(response, 404, "404 Not Found");
|
return http_error(response, 404, "404 Not Found");
|
||||||
}
|
}
|
||||||
@@ -123,23 +94,10 @@ http_request = function (request, response) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Select 'binary' or 'base64' subprotocol, preferring 'binary'
|
|
||||||
selectProtocol = function(protocols, callback) {
|
|
||||||
if (protocols.indexOf('binary') >= 0) {
|
|
||||||
callback(true, 'binary');
|
|
||||||
} else if (protocols.indexOf('base64') >= 0) {
|
|
||||||
callback(true, 'base64');
|
|
||||||
} else {
|
|
||||||
console.log("Client must support 'binary' or 'base64' protocol");
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse source and target arguments into parts
|
// parse source and target arguments into parts
|
||||||
|
source_arg = argv._[0].toString();
|
||||||
|
target_arg = argv._[1].toString();
|
||||||
try {
|
try {
|
||||||
source_arg = argv._[0].toString();
|
|
||||||
target_arg = argv._[1].toString();
|
|
||||||
|
|
||||||
var idx;
|
var idx;
|
||||||
idx = source_arg.indexOf(":");
|
idx = source_arg.indexOf(":");
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
@@ -161,7 +119,7 @@ try {
|
|||||||
throw("illegal port");
|
throw("illegal port");
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error("websockify.js [--web web_dir] [--cert cert.pem [--key key.pem]] [source_addr:]source_port target_addr:target_port");
|
console.error("wsproxy.py [source_addr:]source_port target_addr:target_port");
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,21 +130,11 @@ if (argv.web) {
|
|||||||
console.log(" - Web server active. Serving: " + argv.web);
|
console.log(" - Web server active. Serving: " + argv.web);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.cert) {
|
httpServer = http.createServer(http_request);
|
||||||
argv.key = argv.key || argv.cert;
|
httpServer.listen(source_port, function() {
|
||||||
var cert = fs.readFileSync(argv.cert),
|
wsServer = new WebSocketServer({server: httpServer});
|
||||||
key = fs.readFileSync(argv.key);
|
|
||||||
console.log(" - Running in encrypted HTTPS (wss://) mode using: " + argv.cert + ", " + argv.key);
|
|
||||||
webServer = https.createServer({cert: cert, key: key}, http_request);
|
|
||||||
} else {
|
|
||||||
console.log(" - Running in unencrypted HTTP (ws://) mode");
|
|
||||||
webServer = http.createServer(http_request);
|
|
||||||
}
|
|
||||||
webServer.listen(source_port, function() {
|
|
||||||
wsServer = new WebSocketServer({server: webServer,
|
|
||||||
handleProtocols: selectProtocol});
|
|
||||||
wsServer.on('connection', new_client);
|
wsServer.on('connection', new_client);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attach Flash policyfile answer service
|
// Attach Flash policyfile answer service
|
||||||
policyfile.createServer().listen(-1, webServer);
|
policyfile.createServer().listen(-1, httpServer);
|
||||||
+3
-3
@@ -39,7 +39,7 @@ Traffic Legend:
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Echo back whatever is received
|
# Echo back whatever is received
|
||||||
def new_websocket_client(client)
|
def new_client(client)
|
||||||
|
|
||||||
msg "connecting to: %s:%s" % [@target_host, @target_port]
|
msg "connecting to: %s:%s" % [@target_host, @target_port]
|
||||||
tsock = TCPSocket.open(@target_host, @target_port)
|
tsock = TCPSocket.open(@target_host, @target_port)
|
||||||
@@ -92,7 +92,7 @@ Traffic Legend:
|
|||||||
# Receive target data and queue for the client
|
# Receive target data and queue for the client
|
||||||
if ins && ins.include?(target)
|
if ins && ins.include?(target)
|
||||||
buf = target.recv(@@Buffer_size)
|
buf = target.recv(@@Buffer_size)
|
||||||
if buf.length == 0
|
if buf.length == 0:
|
||||||
raise EClose, "Target closed"
|
raise EClose, "Target closed"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ parser = OptionParser.new do |o|
|
|||||||
o.parse!
|
o.parse!
|
||||||
end
|
end
|
||||||
|
|
||||||
if ARGV.length < 2
|
if ARGV.length < 2:
|
||||||
puts "Too few arguments"
|
puts "Too few arguments"
|
||||||
exit 2
|
exit 2
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
* REBIND_PORT_NEW environment variables are set then bind on the new
|
* REBIND_PORT_NEW environment variables are set then bind on the new
|
||||||
* port (of localhost) instead of the old port.
|
* port (of localhost) instead of the old port.
|
||||||
*
|
*
|
||||||
* This allows a bridge/proxy (such as websockify) to run on the old port and
|
* This allows a proxy (such as wsproxy) to run on the old port and translate
|
||||||
* translate traffic to/from the new port.
|
* traffic to/from the new port.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* LD_PRELOAD=./rebind.so \
|
* LD_PRELOAD=./rebind.so \
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import websockify
|
|
||||||
|
|
||||||
websockify.websocketproxy.websockify_init()
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = '0.6.1'
|
version = '0.1.0'
|
||||||
name = 'websockify'
|
name = 'websockify'
|
||||||
long_description = open("README.md").read() + "\n" + \
|
long_description = open("README.md").read() + "\n" + \
|
||||||
open("CHANGES.txt").read() + "\n"
|
open("CHANGES.txt").read() + "\n"
|
||||||
@@ -12,27 +12,19 @@ setup(name=name,
|
|||||||
classifiers=[
|
classifiers=[
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
],
|
],
|
||||||
data_files=[('share/websockify/include',
|
|
||||||
['include/util.js',
|
|
||||||
'include/base64.js',
|
|
||||||
'include/websock.js']),
|
|
||||||
('share/websockify/include/web-socket-js',
|
|
||||||
['include/web-socket-js/WebSocketMain.swf',
|
|
||||||
'include/web-socket-js/swfobject.js',
|
|
||||||
'include/web-socket-js/web_socket.js'])],
|
|
||||||
keywords='noVNC websockify',
|
keywords='noVNC websockify',
|
||||||
license='LGPLv3',
|
license='LGPLv3',
|
||||||
url="https://github.com/kanaka/websockify",
|
url="https://github.com/kanaka/websockify",
|
||||||
author="Joel Martin",
|
author="Joel Martin",
|
||||||
author_email="github@martintribe.org",
|
author_email="github@martintribe.org",
|
||||||
|
|
||||||
packages=['websockify'],
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['numpy'],
|
install_requires=['numpy'],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'websockify = websockify.websocketproxy:websockify_init',
|
'websockify = websockify:websockify_init',
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+11
-12
@@ -10,17 +10,17 @@ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
|||||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os, sys, select, optparse, logging
|
import os, sys, select, optparse
|
||||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
||||||
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
from websocket import WebSocketServer
|
||||||
|
|
||||||
class WebSocketEcho(WebSocketRequestHandler):
|
class WebSocketEcho(WebSocketServer):
|
||||||
"""
|
"""
|
||||||
WebSockets server that echos back whatever is received from the
|
WebSockets server that echos back whatever is received from the
|
||||||
client. """
|
client. """
|
||||||
buffer_size = 8096
|
buffer_size = 8096
|
||||||
|
|
||||||
def new_websocket_client(self):
|
def new_client(self):
|
||||||
"""
|
"""
|
||||||
Echo back whatever is received.
|
Echo back whatever is received.
|
||||||
"""
|
"""
|
||||||
@@ -28,27 +28,28 @@ class WebSocketEcho(WebSocketRequestHandler):
|
|||||||
cqueue = []
|
cqueue = []
|
||||||
c_pend = 0
|
c_pend = 0
|
||||||
cpartial = ""
|
cpartial = ""
|
||||||
rlist = [self.request]
|
rlist = [self.client]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
wlist = []
|
wlist = []
|
||||||
|
|
||||||
if cqueue or c_pend: wlist.append(self.request)
|
if cqueue or c_pend: wlist.append(self.client)
|
||||||
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
||||||
if excepts: raise Exception("Socket exception")
|
if excepts: raise Exception("Socket exception")
|
||||||
|
|
||||||
if self.request in outs:
|
if self.client in outs:
|
||||||
# Send queued target data to the client
|
# Send queued target data to the client
|
||||||
c_pend = self.send_frames(cqueue)
|
c_pend = self.send_frames(cqueue)
|
||||||
cqueue = []
|
cqueue = []
|
||||||
|
|
||||||
if self.request in ins:
|
if self.client in ins:
|
||||||
# Receive client data, decode it, and send it back
|
# Receive client data, decode it, and send it back
|
||||||
frames, closed = self.recv_frames()
|
frames, closed = self.recv_frames()
|
||||||
cqueue.extend(frames)
|
cqueue.extend(frames)
|
||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
self.send_close()
|
self.send_close()
|
||||||
|
raise self.EClose(closed)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
||||||
@@ -68,9 +69,7 @@ if __name__ == '__main__':
|
|||||||
except:
|
except:
|
||||||
parser.error("Invalid arguments")
|
parser.error("Invalid arguments")
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
opts.web = "."
|
opts.web = "."
|
||||||
server = WebSocketServer(WebSocketEcho, **opts.__dict__)
|
server = WebSocketEcho(**opts.__dict__)
|
||||||
server.start_server()
|
server.start_server()
|
||||||
|
|
||||||
|
|||||||
+3
-10
@@ -12,7 +12,7 @@ require 'websocket'
|
|||||||
class WebSocketEcho < WebSocketServer
|
class WebSocketEcho < WebSocketServer
|
||||||
|
|
||||||
# Echo back whatever is received
|
# Echo back whatever is received
|
||||||
def new_websocket_client(client)
|
def new_client(client)
|
||||||
|
|
||||||
cqueue = []
|
cqueue = []
|
||||||
c_pend = 0
|
c_pend = 0
|
||||||
@@ -50,17 +50,10 @@ class WebSocketEcho < WebSocketServer
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
port = ARGV[0].to_i || 8080
|
port = ARGV[0].to_i
|
||||||
puts "Starting server on port #{port}"
|
puts "Starting server on port #{port}"
|
||||||
server_cert = nil
|
|
||||||
server_key = nil
|
|
||||||
if ARGV.length > 2
|
|
||||||
server_cert = ARGV[1]
|
|
||||||
server_key = ARGV[2]
|
|
||||||
end
|
|
||||||
|
|
||||||
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true,
|
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true)
|
||||||
'server_cert' => server_cert, 'server_key' => server_key)
|
|
||||||
server.start
|
server.start
|
||||||
server.join
|
server.join
|
||||||
|
|
||||||
|
|||||||
+15
-18
@@ -6,37 +6,35 @@ that has a random payload (length and content) that is checksummed and
|
|||||||
given a sequence number. Any errors are reported and counted.
|
given a sequence number. Any errors are reported and counted.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import sys, os, select, random, time, optparse, logging
|
import sys, os, select, random, time, optparse
|
||||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
||||||
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
from websocket import WebSocketServer
|
||||||
|
|
||||||
class WebSocketLoadServer(WebSocketServer):
|
class WebSocketLoad(WebSocketServer):
|
||||||
|
|
||||||
|
buffer_size = 65536
|
||||||
|
|
||||||
|
max_packet_size = 10000
|
||||||
recv_cnt = 0
|
recv_cnt = 0
|
||||||
send_cnt = 0
|
send_cnt = 0
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.errors = 0
|
||||||
self.delay = kwargs.pop('delay')
|
self.delay = kwargs.pop('delay')
|
||||||
|
|
||||||
WebSocketServer.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketLoad(WebSocketRequestHandler):
|
|
||||||
|
|
||||||
max_packet_size = 10000
|
|
||||||
|
|
||||||
def new_websocket_client(self):
|
|
||||||
print "Prepopulating random array"
|
print "Prepopulating random array"
|
||||||
self.rand_array = []
|
self.rand_array = []
|
||||||
for i in range(0, self.max_packet_size):
|
for i in range(0, self.max_packet_size):
|
||||||
self.rand_array.append(random.randint(0, 9))
|
self.rand_array.append(random.randint(0, 9))
|
||||||
|
|
||||||
self.errors = 0
|
WebSocketServer.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def new_client(self):
|
||||||
self.send_cnt = 0
|
self.send_cnt = 0
|
||||||
self.recv_cnt = 0
|
self.recv_cnt = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.responder(self.request)
|
self.responder(self.client)
|
||||||
except:
|
except:
|
||||||
print "accumulated errors:", self.errors
|
print "accumulated errors:", self.errors
|
||||||
self.errors = 0
|
self.errors = 0
|
||||||
@@ -63,13 +61,14 @@ class WebSocketLoad(WebSocketRequestHandler):
|
|||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
self.send_close()
|
self.send_close()
|
||||||
|
raise self.EClose(closed)
|
||||||
|
|
||||||
now = time.time() * 1000
|
now = time.time() * 1000
|
||||||
if client in outs:
|
if client in outs:
|
||||||
if c_pend:
|
if c_pend:
|
||||||
last_send = now
|
last_send = now
|
||||||
c_pend = self.send_frames()
|
c_pend = self.send_frames()
|
||||||
elif now > (last_send + self.server.delay):
|
elif now > (last_send + self.delay):
|
||||||
last_send = now
|
last_send = now
|
||||||
c_pend = self.send_frames([self.generate()])
|
c_pend = self.send_frames([self.generate()])
|
||||||
|
|
||||||
@@ -162,9 +161,7 @@ if __name__ == '__main__':
|
|||||||
except:
|
except:
|
||||||
parser.error("Invalid arguments")
|
parser.error("Invalid arguments")
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
opts.web = "."
|
opts.web = "."
|
||||||
server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__)
|
server = WebSocketLoad(**opts.__dict__)
|
||||||
server.start_server()
|
server.start_server()
|
||||||
|
|
||||||
|
|||||||
@@ -1,243 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# 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
|
|
||||||
+7
-4
@@ -5,15 +5,18 @@ Display UTF-8 encoding for 0-255.'''
|
|||||||
|
|
||||||
import sys, os, socket, ssl, time, traceback
|
import sys, os, socket, ssl, time, traceback
|
||||||
from select import select
|
from select import select
|
||||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
|
||||||
from websockify.websocket import WebSocketServer
|
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
||||||
|
from websocket import WebSocketServer
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print "val: hybi_base64 | hybi_binary"
|
print "val: hixie | hybi_base64 | hybi_binary"
|
||||||
for c in range(0, 256):
|
for c in range(0, 256):
|
||||||
|
hixie = WebSocketServer.encode_hixie(chr(c))
|
||||||
hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1,
|
hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1,
|
||||||
base64=True)
|
base64=True)
|
||||||
hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2,
|
hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2,
|
||||||
base64=False)
|
base64=False)
|
||||||
print "%d: %s | %s" % (c, repr(hybi_base64), repr(hybi_binary))
|
print "%d: %s | %s | %s" % (c, repr(hixie), repr(hybi_base64),
|
||||||
|
repr(hybi_binary))
|
||||||
|
|
||||||
|
|||||||
+946
@@ -0,0 +1,946 @@
|
|||||||
|
#!/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
|
||||||
@@ -11,20 +11,21 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import signal, socket, optparse, time, os, sys, subprocess, logging
|
import socket, optparse, time, os, sys, subprocess
|
||||||
try: from socketserver import ForkingMixIn
|
|
||||||
except: from SocketServer import ForkingMixIn
|
|
||||||
try: from http.server import HTTPServer
|
|
||||||
except: from BaseHTTPServer import HTTPServer
|
|
||||||
from select import select
|
from select import select
|
||||||
import websocket
|
import websocket
|
||||||
try:
|
try: from urllib.parse import parse_qs, urlparse
|
||||||
from urllib.parse import parse_qs, urlparse
|
except: from urlparse import parse_qs, urlparse
|
||||||
except:
|
|
||||||
from cgi import parse_qs
|
|
||||||
from urlparse import urlparse
|
|
||||||
|
|
||||||
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
|
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
|
||||||
|
|
||||||
traffic_legend = """
|
traffic_legend = """
|
||||||
Traffic Legend:
|
Traffic Legend:
|
||||||
@@ -38,33 +39,148 @@ Traffic Legend:
|
|||||||
<. - Client send partial
|
<. - Client send partial
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def new_websocket_client(self):
|
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):
|
||||||
"""
|
"""
|
||||||
Called after a new WebSocket connection has been established.
|
Called after a new WebSocket connection has been established.
|
||||||
"""
|
"""
|
||||||
# Checks if we receive a token, and look
|
# Checks if we receive a token, and look
|
||||||
# for a valid target for it then
|
# for a valid target for it then
|
||||||
if self.server.target_cfg:
|
if self.target_cfg:
|
||||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.target_cfg, self.path)
|
(self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path)
|
||||||
|
|
||||||
# Connect to the target
|
# Connect to the target
|
||||||
if self.server.wrap_cmd:
|
if self.wrap_cmd:
|
||||||
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port)
|
msg = "connecting to command: %s" % (" ".join(self.wrap_cmd), self.target_port)
|
||||||
elif self.server.unix_target:
|
elif self.unix_target:
|
||||||
msg = "connecting to unix socket: %s" % self.server.unix_target
|
msg = "connecting to unix socket: %s" % self.unix_target
|
||||||
else:
|
else:
|
||||||
msg = "connecting to: %s:%s" % (
|
msg = "connecting to: %s:%s" % (
|
||||||
self.server.target_host, self.server.target_port)
|
self.target_host, self.target_port)
|
||||||
|
|
||||||
if self.server.ssl_target:
|
if self.ssl_target:
|
||||||
msg += " (using SSL)"
|
msg += " (using SSL)"
|
||||||
self.log_message(msg)
|
self.msg(msg)
|
||||||
|
|
||||||
tsock = websocket.WebSocketServer.socket(self.server.target_host,
|
tsock = self.socket(self.target_host, self.target_port,
|
||||||
self.server.target_port,
|
connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target)
|
||||||
connect=True, use_ssl=self.server.ssl_target, unix_socket=self.server.unix_target)
|
|
||||||
|
|
||||||
self.print_traffic(self.traffic_legend)
|
if self.verbose and not self.daemon:
|
||||||
|
print(self.traffic_legend)
|
||||||
|
|
||||||
# Start proxying
|
# Start proxying
|
||||||
try:
|
try:
|
||||||
@@ -73,9 +189,8 @@ Traffic Legend:
|
|||||||
if tsock:
|
if tsock:
|
||||||
tsock.shutdown(socket.SHUT_RDWR)
|
tsock.shutdown(socket.SHUT_RDWR)
|
||||||
tsock.close()
|
tsock.close()
|
||||||
if self.verbose:
|
self.vmsg("%s:%s: Closed target" %(
|
||||||
self.log_message("%s:%s: Closed target" %(
|
self.target_host, self.target_port))
|
||||||
self.server.target_host, self.server.target_port))
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get_target(self, target_cfg, path):
|
def get_target(self, target_cfg, path):
|
||||||
@@ -90,7 +205,7 @@ Traffic Legend:
|
|||||||
# Extract the token parameter from url
|
# Extract the token parameter from url
|
||||||
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
||||||
|
|
||||||
if not args.has_key('token') or not len(args['token']):
|
if not len(args['token']):
|
||||||
raise self.EClose("Token not present")
|
raise self.EClose("Token not present")
|
||||||
|
|
||||||
token = args['token'][0].rstrip('\n')
|
token = args['token'][0].rstrip('\n')
|
||||||
@@ -124,203 +239,66 @@ Traffic Legend:
|
|||||||
cqueue = []
|
cqueue = []
|
||||||
c_pend = 0
|
c_pend = 0
|
||||||
tqueue = []
|
tqueue = []
|
||||||
rlist = [self.request, target]
|
rlist = [self.client, target]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
wlist = []
|
wlist = []
|
||||||
|
|
||||||
if tqueue: wlist.append(target)
|
if tqueue: wlist.append(target)
|
||||||
if cqueue or c_pend: wlist.append(self.request)
|
if cqueue or c_pend: wlist.append(self.client)
|
||||||
ins, outs, excepts = select(rlist, wlist, [], 1)
|
ins, outs, excepts = select(rlist, wlist, [], 1)
|
||||||
if excepts: raise Exception("Socket exception")
|
if excepts: raise Exception("Socket exception")
|
||||||
|
|
||||||
if self.request in outs:
|
|
||||||
# Send queued target data to the client
|
|
||||||
c_pend = self.send_frames(cqueue)
|
|
||||||
|
|
||||||
cqueue = []
|
|
||||||
|
|
||||||
if self.request in ins:
|
|
||||||
# Receive client data, decode it, and queue for target
|
|
||||||
bufs, closed = self.recv_frames()
|
|
||||||
tqueue.extend(bufs)
|
|
||||||
|
|
||||||
if closed:
|
|
||||||
# TODO: What about blocking on client socket?
|
|
||||||
if self.verbose:
|
|
||||||
self.log_message("%s:%s: Client closed connection" %(
|
|
||||||
self.server.target_host, self.server.target_port))
|
|
||||||
raise self.CClose(closed['code'], closed['reason'])
|
|
||||||
|
|
||||||
|
|
||||||
if target in outs:
|
if target in outs:
|
||||||
# Send queued client data to the target
|
# Send queued client data to the target
|
||||||
dat = tqueue.pop(0)
|
dat = tqueue.pop(0)
|
||||||
sent = target.send(dat)
|
sent = target.send(dat)
|
||||||
if sent == len(dat):
|
if sent == len(dat):
|
||||||
self.print_traffic(">")
|
self.traffic(">")
|
||||||
else:
|
else:
|
||||||
# requeue the remaining data
|
# requeue the remaining data
|
||||||
tqueue.insert(0, dat[sent:])
|
tqueue.insert(0, dat[sent:])
|
||||||
self.print_traffic(".>")
|
self.traffic(".>")
|
||||||
|
|
||||||
|
|
||||||
if target in ins:
|
if target in ins:
|
||||||
# Receive target data, encode it and queue for client
|
# Receive target data, encode it and queue for client
|
||||||
buf = target.recv(self.buffer_size)
|
buf = target.recv(self.buffer_size)
|
||||||
if len(buf) == 0:
|
if len(buf) == 0:
|
||||||
if self.verbose:
|
self.vmsg("%s:%s: Target closed connection" %(
|
||||||
self.log_message("%s:%s: Target closed connection" %(
|
self.target_host, self.target_port))
|
||||||
self.server.target_host, self.server.target_port))
|
|
||||||
raise self.CClose(1000, "Target closed")
|
raise self.CClose(1000, "Target closed")
|
||||||
|
|
||||||
cqueue.append(buf)
|
cqueue.append(buf)
|
||||||
self.print_traffic("{")
|
self.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():
|
if self.client in outs:
|
||||||
# Python installs a SIGPIPE handler by default. This is usually not what
|
# Send queued target data to the client
|
||||||
# non-Python successfulbprocesses expect.
|
c_pend = self.send_frames(cqueue)
|
||||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
||||||
|
cqueue = []
|
||||||
|
|
||||||
|
|
||||||
def logger_init():
|
if self.client in ins:
|
||||||
logger = logging.getLogger(WebSocketProxy.log_prefix)
|
# Receive client data, decode it, and queue for target
|
||||||
logger.propagate = False
|
bufs, closed = self.recv_frames()
|
||||||
logger.setLevel(logging.INFO)
|
tqueue.extend(bufs)
|
||||||
h = logging.StreamHandler()
|
|
||||||
h.setLevel(logging.DEBUG)
|
|
||||||
h.setFormatter(logging.Formatter("%(message)s"))
|
|
||||||
logger.addHandler(h)
|
|
||||||
|
|
||||||
|
if closed:
|
||||||
|
# TODO: What about blocking on client socket?
|
||||||
|
self.vmsg("%s:%s: Client closed connection" %(
|
||||||
|
self.target_host, self.target_port))
|
||||||
|
raise self.CClose(closed['code'], closed['reason'])
|
||||||
|
|
||||||
def websockify_init():
|
def websockify_init():
|
||||||
logger_init()
|
|
||||||
|
|
||||||
usage = "\n %prog [options]"
|
usage = "\n %prog [options]"
|
||||||
usage += " [source_addr:]source_port [target_addr:target_port]"
|
usage += " [source_addr:]source_port [target_addr:target_port]"
|
||||||
usage += "\n %prog [options]"
|
usage += "\n %prog [options]"
|
||||||
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
||||||
parser = optparse.OptionParser(usage=usage)
|
parser = optparse.OptionParser(usage=usage)
|
||||||
parser.add_option("--verbose", "-v", action="store_true",
|
parser.add_option("--verbose", "-v", action="store_true",
|
||||||
help="verbose messages")
|
help="verbose messages and per frame traffic")
|
||||||
parser.add_option("--traffic", action="store_true",
|
|
||||||
help="per frame traffic")
|
|
||||||
parser.add_option("--record",
|
parser.add_option("--record",
|
||||||
help="record sessions to FILE.[session_number]", metavar="FILE")
|
help="record sessions to FILE.[session_number]", metavar="FILE")
|
||||||
parser.add_option("--daemon", "-D",
|
parser.add_option("--daemon", "-D",
|
||||||
@@ -330,9 +308,6 @@ def websockify_init():
|
|||||||
help="handle a single WebSocket connection and exit")
|
help="handle a single WebSocket connection and exit")
|
||||||
parser.add_option("--timeout", type=int, default=0,
|
parser.add_option("--timeout", type=int, default=0,
|
||||||
help="after TIMEOUT seconds exit when not connected")
|
help="after TIMEOUT seconds exit when not connected")
|
||||||
parser.add_option("--idle-timeout", type=int, default=0,
|
|
||||||
help="server exits after TIMEOUT seconds if there are no "
|
|
||||||
"active connections")
|
|
||||||
parser.add_option("--cert", default="self.pem",
|
parser.add_option("--cert", default="self.pem",
|
||||||
help="SSL certificate file")
|
help="SSL certificate file")
|
||||||
parser.add_option("--key", default=None,
|
parser.add_option("--key", default=None,
|
||||||
@@ -357,15 +332,10 @@ def websockify_init():
|
|||||||
help="Configuration file containing valid targets "
|
help="Configuration file containing valid targets "
|
||||||
"in the form 'token: host:port' or, alternatively, a "
|
"in the form 'token: host:port' or, alternatively, a "
|
||||||
"directory containing configuration files of this form")
|
"directory containing configuration files of this form")
|
||||||
parser.add_option("--libserver", action="store_true",
|
|
||||||
help="use Python library SocketServer engine")
|
|
||||||
(opts, args) = parser.parse_args()
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
if opts.verbose:
|
|
||||||
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
|
if len(args) < 2 and not opts.target_cfg:
|
||||||
parser.error("Too few arguments")
|
parser.error("Too few arguments")
|
||||||
if sys.argv.count('--'):
|
if sys.argv.count('--'):
|
||||||
opts.wrap_cmd = args[1:]
|
opts.wrap_cmd = args[1:]
|
||||||
@@ -376,7 +346,7 @@ def websockify_init():
|
|||||||
|
|
||||||
if not websocket.ssl and opts.ssl_target:
|
if not websocket.ssl and opts.ssl_target:
|
||||||
parser.error("SSL target requested and Python SSL module not loaded.");
|
parser.error("SSL target requested and Python SSL module not loaded.");
|
||||||
|
|
||||||
if opts.ssl_only and not os.path.exists(opts.cert):
|
if opts.ssl_only and not os.path.exists(opts.cert):
|
||||||
parser.error("SSL only and %s not found" % opts.cert)
|
parser.error("SSL only and %s not found" % opts.cert)
|
||||||
|
|
||||||
@@ -402,70 +372,9 @@ def websockify_init():
|
|||||||
try: opts.target_port = int(opts.target_port)
|
try: opts.target_port = int(opts.target_port)
|
||||||
except: parser.error("Error parsing target port")
|
except: parser.error("Error parsing target port")
|
||||||
|
|
||||||
# Transform to absolute path as daemon may chdir
|
|
||||||
if opts.target_cfg:
|
|
||||||
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
|
||||||
|
|
||||||
# Create and start the WebSockets proxy
|
# Create and start the WebSockets proxy
|
||||||
libserver = opts.libserver
|
server = WebSocketProxy(**opts.__dict__)
|
||||||
del opts.libserver
|
server.start_server()
|
||||||
if libserver:
|
|
||||||
# Use standard Python SocketServer framework
|
|
||||||
server = LibProxyServer(**opts.__dict__)
|
|
||||||
server.serve_forever()
|
|
||||||
else:
|
|
||||||
# Use internal service framework
|
|
||||||
server = WebSocketProxy(**opts.__dict__)
|
|
||||||
server.start_server()
|
|
||||||
|
|
||||||
|
|
||||||
class LibProxyServer(ForkingMixIn, HTTPServer):
|
|
||||||
"""
|
|
||||||
Just like WebSocketProxy, but uses standard Python SocketServer
|
|
||||||
framework.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs):
|
|
||||||
# Save off proxy specific options
|
|
||||||
self.target_host = kwargs.pop('target_host', None)
|
|
||||||
self.target_port = kwargs.pop('target_port', None)
|
|
||||||
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
|
|
||||||
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
|
||||||
self.unix_target = kwargs.pop('unix_target', None)
|
|
||||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
|
||||||
self.target_cfg = kwargs.pop('target_cfg', None)
|
|
||||||
self.daemon = False
|
|
||||||
self.target_cfg = None
|
|
||||||
|
|
||||||
# Server configuration
|
|
||||||
listen_host = kwargs.pop('listen_host', '')
|
|
||||||
listen_port = kwargs.pop('listen_port', None)
|
|
||||||
web = kwargs.pop('web', '')
|
|
||||||
|
|
||||||
# Configuration affecting base request handler
|
|
||||||
self.only_upgrade = not web
|
|
||||||
self.verbose = kwargs.pop('verbose', False)
|
|
||||||
record = kwargs.pop('record', '')
|
|
||||||
if record:
|
|
||||||
self.record = os.path.abspath(record)
|
|
||||||
self.run_once = kwargs.pop('run_once', False)
|
|
||||||
self.handler_id = 0
|
|
||||||
|
|
||||||
for arg in kwargs.keys():
|
|
||||||
print("warning: option %s ignored when using --libserver" % arg)
|
|
||||||
|
|
||||||
if web:
|
|
||||||
os.chdir(web)
|
|
||||||
|
|
||||||
HTTPServer.__init__(self, (listen_host, listen_port),
|
|
||||||
RequestHandlerClass)
|
|
||||||
|
|
||||||
|
|
||||||
def process_request(self, request, client_address):
|
|
||||||
"""Override process_request to implement a counter"""
|
|
||||||
self.handler_id += 1
|
|
||||||
ForkingMixIn.process_request(self, request, client_address)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
websockify_init()
|
websockify_init()
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
run
|
websockify
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
from websocket import *
|
|
||||||
from websocketproxy import *
|
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user