54 Commits

Author SHA1 Message Date
Pierre Ossman 71d55fcaa8 Fix bad classifiers array 2021-07-22 17:20:45 +02:00
Pierre Ossman 1c35fdd72b websockify 0.10.0 2021-07-22 17:11:08 +02:00
Pierre Ossman eca301c05b Fix patching of open() for Python 3.4
It doesn't handle builtins automatically, so follow the recommendations
from that time.
2021-05-03 14:34:07 +02:00
Pierre Ossman b9b269c73f Add unit test for token file with tab 2021-04-16 13:02:07 +02:00
Pierre Ossman da130946eb Merge branch 'sisou/split-tokens-tab' of https://github.com/sisou/websockify 2021-04-16 13:00:36 +02:00
Pierre Ossman 100a776409 Add unit tests for ReadOnlyTokenFile 2021-04-16 12:59:17 +02:00
Pierre Ossman 984dcc62d3 Move JWT token plugins tests to separate file
Let's try to match the test units with the modules we have.
2021-04-16 11:27:56 +02:00
Samuel Mannehed c5d365dd1d Merge pull request #479 from llopisdon/patch-1
Update README.md
2021-03-22 09:23:15 +01:00
Don E. Llopis 27e0ce71ed Update README.md
Fixed typo and clarified sentence.
2021-03-20 10:17:55 -04:00
Pierre Ossman 24c3f75ede Remove setuptools version restriction
We're fine with current versions now.
2021-02-08 15:51:34 +01:00
Pierre Ossman 8b724074e0 Use older PROTOCOL_SSLv23 instead of PROTOCOL_TLS
We need to support Python older than 3.6, so use the older name.
2021-02-08 15:47:29 +01:00
Pierre Ossman 55f1fe2603 Explicitly include "six" for jwt tests
It is not properly listed as a dependency for jwcrypto, so we need to
pull it in manually.
2021-02-08 15:46:43 +01:00
Pierre Ossman f1e33fa4c7 Use older numpy for older Python versions
The latest version refuses to run on anything older than Python 3.7.
2021-02-08 15:33:37 +01:00
Pierre Ossman 72704b3fe8 Update classifiers to just Python 3
We no longer support anything older than Python 3.4.
2021-02-08 14:55:49 +01:00
Pierre Ossman 4afdcb00ab Update references in egg manifest
We changed things when we purged all the non-Python stuff out of the
tree.
2021-02-08 14:55:15 +01:00
Pierre Ossman 982e475936 Run all tests even on failure
Issues might be specific to certain versions of Python, so let all tests
run.
2021-02-08 14:42:41 +01:00
Pierre Ossman 6012052686 Use Ubuntu 18.04 for tests
We want to test on Python 3.4, which requires us to use an older Ubuntu
image for tests.
2021-02-08 11:11:39 +01:00
Pierre Ossman e4e85f8082 Replace Travis with Github actions 2021-02-08 11:02:48 +01:00
Pierre Ossman f7b0867471 Merge branch 'fix467_redis' of https://github.com/PexMor/websockify 2021-02-08 10:42:01 +01:00
PexMor 9dab8b360b add details on format of redis and leave port as number 2021-02-07 20:39:02 +01:00
Alexander E. Patrakov 4afafcd152 Simplify Windows instructions
Instead of a Websockify-specific tool that needs to be compiled, and
that doesn't work on Windows 10 anyway (the service takes forever to
start), let's switch to the industry standard SrvAny tool. This also
gets rid of .Net Framework requirement and lets one run modern
versions of Websockify easily.
2021-01-30 17:11:20 +05:00
Pierre Ossman b22a6b0ce0 Add unit tests for jwt token time checks 2021-01-29 13:11:07 +01:00
Pierre Ossman 980237c9cd Merge branch 'new_branch' of https://github.com/jprietove/websockify 2021-01-29 13:10:49 +01:00
Pierre Ossman fadb25e026 Use assertRaises() as a context manager
Makes the code a lot easier to read.
2021-01-29 13:10:16 +01:00
Pierre Ossman 3f17696dc6 Use patch() as a decorator in tests
Cleaner and more robust.
2021-01-29 13:09:19 +01:00
Pierre Ossman a82eb10b48 Remove Python version check
We require Python 3 now, so no need for this check.
2021-01-29 12:58:26 +01:00
Javier Prieto 1f618c8f41 Added JWT nbf, not before 2021-01-27 16:50:19 +01:00
Javier Prieto e9367b03be Added exp claim for JWT token 2021-01-27 15:16:08 +01:00
Tim Gates adc278657a docs: fix simple typo, acccept -> accept
There is a small typo in README.md.

Should read `accept` rather than `acccept`.
2020-12-25 20:37:15 +11:00
Pierre Ossman 108f993eb3 Explicitly request Python 3
It varies between systems if /usr/bin/python is Python 2.x or Python
3.x. Since we now only work with Python 3.x we should be more explicit
in our scripts and documentation.
2020-12-15 16:10:43 +01:00
Pierre Ossman 33710b3972 Use frombytes() instead of fromstring()
Both the Python library and NumPy have changed the names of these
functions to better match the new bytes data type it actually uses.
2020-12-14 16:08:16 +01:00
Pierre Ossman 96eda1a5c7 Remove support for older Python
All active distributions should now support at least Python 3.4, so
let's clean things up by removing older compatibility code.
2020-12-14 13:48:54 +01:00
Pierre Ossman e80739b296 Add more search paths for rebind.so
If we're run as a module then sys.argv[0] will be the main module, not
some startup script.
2020-12-04 13:57:21 +01:00
Samuel Mannehed 4c2edaabc2 Merge pull request #459 from nwtgck/patch-1
fix a typo
2020-11-18 09:15:42 +01:00
Ryo Ota d2155697fc fix a typo 2020-11-18 08:53:09 +09:00
JasonYang 2b44803f83 fixed a bug in the function of send_frames. 2020-11-10 13:14:54 +01:00
Pierre Ossman 01ef6a6a55 Require same arguments on insufficient socket space
This matches the behaviour of SSLSocket, which we are trying to mimic.
It also closely matches the behaviour of normal Socket which can be
assumed to not have sent anything if an error occurs. We might actually
send some data, but the caller cannot really see that and must call us
again as if no data was sent.
2020-09-30 14:58:19 +02:00
Pierre Ossman 6caf23c067 Add type checking when sendning data
We use this in various ways so add an early check to make things clear
in case anything is called incorrectly.
2020-09-30 14:53:19 +02:00
Pierre Ossman d72ace2ae6 Allow sending empty messages
This is perfectly valid in the protocol, and may be meningful to some
applications. However send() is still stream oriented so it will ignore
an empty buffer.
2020-09-30 14:51:07 +02:00
Samuel Mannehed 536a548db5 Merge pull request #441 from RangeForce/python-gitignore
Add build and dist directories to .gitignore
2020-09-02 02:19:12 +02:00
Chris Kerr 997b3897e7 Add build and dist directories to .gitignore 2020-09-01 10:26:46 +03:00
Samuel Mannehed 7d4841c49a Merge pull request #440 from gamtiq/patch-1
Fix a typo in README
2020-08-25 16:51:00 +02:00
Denis Sikuler 9851dd9012 Fix a typo in README 2020-08-24 15:36:35 +03:00
Pierre Ossman 9b1780f068 Fix bad reset of fragmented frame
The Python 2/3 binary data type compatibility hack was backwards here.
2020-08-21 10:58:55 +02:00
Pierre Ossman e5b6dfe17f Merge branch 'sisou/fix-print-statement' of https://github.com/sisou/websockify 2020-08-21 10:51:37 +02:00
Pierre Ossman 992e09eac4 Convert tests from mox to mock
mox is deprecated upstream in favour of mock
2020-08-21 10:50:11 +02:00
Sören Schwert 663b39cd38 Split token file by any whitespace after the colon, not just a space
With the current parser logic, only tokens and servers that are separated by _exactly_ a colon and a space `: ` are detected as tokens. But when formatting one's token file with tabs, this breaks.

This commit changes the split characters to be a regular expression that matches all forms of whitespace, including spaces and tabs.
2020-07-28 14:40:42 +02:00
Sören Schwert 57be17d7f5 Convert old print statement into function
For Python 3 compatibility.
2020-07-28 14:35:49 +02:00
Pierre Ossman 86a20b23f5 Update test requirements
Travis are dropping older Python environments, so update things to
something that reflects what current distributions are using (e.g. Red
Hat Enterprise Linux 7).
2020-04-08 09:18:09 +02:00
Feng Ye 7bd955a379 Add UnixDomainSocketDirectory token plugin 2020-03-18 10:40:18 +08:00
Pierre Ossman 6d48b1507e Make sure port is a simple string (again)
The redis token plugin also needs to make sure the port string isn't
unicode for compatibility with Python 2.x.
2020-02-27 15:55:35 +01:00
Pierre Ossman 76897b5093 Log connection errors better 2020-02-27 15:54:29 +01:00
Samuel Mannehed 4ecff119c4 Merge pull request #405 from andersk/shell
run: Fix shell scripting bugs
2019-09-26 08:42:17 +02:00
Anders Kaseorg 5052f823d7 run: Fix shell scripting bugs
* Use double quotes around `"$@"` to fix invocation with arguments
  including spaces.
* Use double quotes around `"$(dirname "$0")"` to fix invocation
  inside a directory path including spaces.
* Use `set -e` to abort in case `cd` fails.
* Use `exec` to avoid forking an unnecessary wrapper process.
* Skip an unnecessary `cd` → `pwd` → `cd` dance, just use `cd`.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2019-08-17 11:39:46 -07:00
31 changed files with 638 additions and 980 deletions
+35
View File
@@ -0,0 +1,35 @@
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-18.04
strategy:
matrix:
python-version:
- 3.4
- 3.5
- 3.6
- 3.7
- 3.8
- 3.9
fail-fast: false
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install setuptools
python -m pip install -r test-requirements.txt
- name: Install old numpy
run: |
python -m pip install 'numpy<1.17'
if: ${{ matrix.python-version >= '3.4' && matrix.python-version < '3.7' }}
- name: Run tests
run: |
python setup.py nosetests --verbosity=3
+3
View File
@@ -6,4 +6,7 @@
target.cfg
target.cfg.d
.tox
build/
dist/
*.egg-info
-15
View File
@@ -1,15 +0,0 @@
language: python
python:
- 2.6
- 2.7
- 3.3
- 3.4
# We need trusty for Python 2.6. We can raise this when we no longer
# care about 2.6.
dist: trusty
install:
- pip install 'setuptools>=18.5,<=39.0.0'
- pip install -r test-requirements.txt
script: python setup.py nosetests --verbosity=3
+10
View File
@@ -1,6 +1,16 @@
Changes
=======
0.10.0
------
* Python 3.4 or newer is now required
* Empty message frames are now supported
* Tokens can now specify a Unix domain socket file to connect to
* Time limits on JWT tokens are now respected
* Whitespace is better tolerated in token files
* Lots of minor fixes...
0.9.0
-----
+1 -2
View File
@@ -1,2 +1 @@
include CHANGES.txt README.md LICENSE.txt
graft include
include CHANGES.txt README.md COPYING
+5 -5
View File
@@ -55,9 +55,9 @@ understand it. You can do this by installing it as accepted certificate, or by
using that same certificate for a HTTPS connection to which you navigate first
and approve. Browsers generally don't give you the "trust certificate?" prompt
by opening a WSS socket with invalid certificate, hence you need to have it
acccept it by either of those two methods.
accept it by either of those two methods.
If you have a commercial/valid SSL certificate with one ore more intermediate
If you have a commercial/valid SSL certificate with one or more intermediate
certificates, concat them into one file, server certificate first, then the
intermediate(s) from the CA, etc. Point to this file with the `--cert` option
and then also to the key with `--key`. Finally, use `--ssl-only` as needed.
@@ -125,7 +125,7 @@ In addition to proxying from a source address to a target address
launch a program on the local system and proxy WebSockets traffic to
a normal TCP port owned/bound by the program.
The is accomplished with a small LD_PRELOAD library (`rebind.so`)
This is accomplished by the LD_PRELOAD library (`rebind.so`)
which intercepts bind() system calls by the program. The specified
port is moved to a new localhost/loopback free high port. websockify
then proxies WebSockets traffic directed to the original port to the
@@ -159,12 +159,12 @@ project demonstrates a simple WebSockets based telnet client (use
### Installing websockify
Download one of the releases or the latest development version, extract
it and run `python setup.py install` as root in the directory where you
it and run `python3 setup.py install` as root in the directory where you
extracted the files. Normally, this will also install numpy for better
performance, if you don't have it installed already. However, numpy is
optional. If you don't want to install numpy or if you can't compile it,
you can edit setup.py and remove the `install_requires=['numpy'],` line
before running `python setup.py install`.
before running `python3 setup.py install`.
Afterwards, websockify should be available in your path. Run
`websockify --help` to confirm it's installed correctly.
+61
View File
@@ -0,0 +1,61 @@
Running Websockify as a Windows service
=======================================
Installation and configuration
------------------------------
Download the following software:
* Python, from https://www.python.org/downloads/windows/
* SrvAny, from http://simpleauto.byethost8.com/Zip/SrvAny.zip
Note that there is [a modern alternative for SrvAny](https://github.com/rwmjones/rhsrvany),
but that project does not provide binaries.
Install Python for all users, not just the current one. Extract Websockify
into a directory, e.g. `C:\Program Files\websockify`, so that e.g.
`README.md` ends up there. Extract the `SrvAny.zip` archive, copy the
`WIN7\SrvAny.exe` file into `C:\Program Files\websockify`.
Then create a batch file, `C:\Program Files\websockify\run.bat`, that runs
Websockify from its directory with the correct options under the correct
Python interpreter:
```
C:
cd "\Program Files\websockify"
"C:\Program Files\Python39\python.exe" -m websockify 5901 127.0.0.1:5900
```
Run it by hand once so that Windows asks you about a firewall exception.
After confirming the exception, press `Ctrl+C` to terminate the script.
Then create a Windows service for Websockify (use an Administrator command
prompt for that). For paths with spaces, like in this example, double-escaping
is needed: once for `cmd.exe` and once for `SrvAny.exe`.
```
C:
cd "\Program Files\websockify"
SrvAny.exe -install Websockify 10s \\\"C:\Program Files\websockify\run.bat\\\"
```
In the Windows Control Panel, under Services, a new "Websockify" service will
appear. In its properties dialog, you can change the startup type, e.g. make
it start automatically at boot. Or, you can start the service manually.
Uninstallation
--------------
If you want to remove the service, first set its startup type to Manual, then
reboot the PC. Then run this command using the Administrator command prompt:
```
C:
cd "\Program Files\websockify"
SrvAny.exe -remove Websockify
```
After that, you will be able to remove the `C:\Program Files\websockify`
directory completely.
-39
View File
@@ -1,39 +0,0 @@
-----------------------------------
Windows noVNC Websockify Service
-----------------------------------
The "noVNC Websocket Service.exe" file is a windows service wrapper created with Visual Studio 2010 to create a windows service to start stop the noVNC Websocket Server. All files used to create the wrapper can be found in 'noVNC Websocket Service Project' folder.
To download the precompiled executables please grab the zip in the downloads section of websockify project:
https://github.com/novnc/websockify
---------------------------
Installation
---------------------------
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
https://github.com/novnc/websockify/wiki/Compiling-Websockify-as-Windows-Executable
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
sc create "noVNC Websocket Server" binPath= "PATH TO noVNC eg C:\noVNC\utils\Windows\Websocket Service.exe" DisplayName= "noVNC Websocket Server"
3 .Once this is run you will be able to access the service via Control Panel > Admin Tools > Services. In here you can specify whether you want the service to run automatically and start at stop the service.
---------------------------
Configuration
---------------------------
The file noVNCConfig.ini must be in the same directory as "noVNC Websocket Service.exe".
This file contains a single line which is the websockify.exe statup arguements. An example is:
192.168.0.1:5901 192.168.0.1:5900
All websockify supported arguements will work if added here.
---------------------------
Deletion
---------------------------
You can delete the service at any time by running the commandline as admin and using this command:
sc delete "noVNC Websocket Server".
@@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
namespace MELT_Command_Websocket
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
}
}
@@ -1,61 +0,0 @@
namespace MELT_Command_Websocket
{
partial class ProjectInstaller
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
//
// serviceProcessInstaller1
//
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.NetworkService;
this.serviceProcessInstaller1.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceInstaller1});
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
//
// serviceInstaller1
//
this.serviceInstaller1.Description = "noVNC Websocket Service";
this.serviceInstaller1.DisplayName = "noVNC Websocket Service";
this.serviceInstaller1.ServiceName = "noVNC Websocket Service";
this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
//
// ProjectInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller1});
}
#endregion
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
}
}
@@ -1,19 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
namespace MELT_Command_Websocket
{
[RunInstaller(true)]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
public ProjectInstaller()
{
InitializeComponent();
}
}
}
@@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="serviceProcessInstaller1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 56</value>
</metadata>
<metadata name="serviceInstaller1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>196, 17</value>
</metadata>
<metadata name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
</root>
@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MELT Command Websocket")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("MELT Command Websocket")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5ab831cb-6852-4ce1-849c-b26725b0e10b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -1,37 +0,0 @@
namespace MELT_Command_Websocket
{
partial class Service1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.ServiceName = "Service1";
}
#endregion
}
}
@@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
namespace MELT_Command_Websocket
{
public partial class Service1 : ServiceBase
{
Process websockify;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
string configpath = AppDomain.CurrentDomain.BaseDirectory + "\\noVNCConfig.ini";
string sockifypath = AppDomain.CurrentDomain.BaseDirectory + "\\websockify.exe";
//Load commandline arguements from config file.
StreamReader streamReader = new StreamReader(configpath);
string arguements = streamReader.ReadLine();
streamReader.Close();
//Start websockify.
websockify = System.Diagnostics.Process.Start(sockifypath, arguements);
}
protected override void OnStop()
{
//Service stopped. Close websockify.
websockify.Kill();
}
}
}
@@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>noVNC_Websocket_Service</RootNamespace>
<AssemblyName>noVNC Websocket Service</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ProjectInstaller.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ProjectInstaller.Designer.cs">
<DependentUpon>ProjectInstaller.cs</DependentUpon>
</Compile>
<Compile Include="Service1.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Service1.Designer.cs">
<DependentUpon>Service1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ProjectInstaller.resx">
<DependentUpon>ProjectInstaller.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -1,20 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "noVNC Websocket", "noVNC Websocket.csproj", "{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Debug|x86.ActiveCfg = Debug|x86
{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Debug|x86.Build.0 = Debug|x86
{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Release|x86.ActiveCfg = Release|x86
{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
+3 -6
View File
@@ -1,7 +1,4 @@
#!/usr/bin/env sh
BASE_DIR="$(cd $(dirname "$0"); pwd)"
cd "$BASE_DIR"
python -m websockify $@
set -e
cd "$(dirname "$0")"
exec python3 -m websockify "$@"
+8 -6
View File
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
version = '0.9.0'
version = '0.10.0'
name = 'websockify'
long_description = open("README.md").read() + "\n" + \
open("CHANGES.txt").read() + "\n"
@@ -12,12 +12,14 @@ setup(name=name,
long_description_content_type="text/markdown",
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4"
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
],
keywords='noVNC websockify',
license='LGPLv3',
+5 -5
View File
@@ -1,6 +1,6 @@
mox3
mock
nose
jwcrypto;python_version>="2.7"
enum34;python_version=="3.3"
redis;python_version>="2.7"
simplejson;python_version>="2.7"
jwcrypto
six
redis
simplejson
+179
View File
@@ -0,0 +1,179 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
""" Unit tests for Token plugins"""
import unittest
from unittest.mock import patch, mock_open, MagicMock
from jwcrypto import jwt
from websockify.token_plugins import ReadOnlyTokenFile, JWTTokenApi
class ReadOnlyTokenFileTestCase(unittest.TestCase):
patch('os.path.isdir', MagicMock(return_value=False))
def test_empty(self):
plugin = ReadOnlyTokenFile('configfile')
config = ""
pyopen = mock_open(read_data=config)
with patch("websockify.token_plugins.open", pyopen, create=True):
result = plugin.lookup('testhost')
pyopen.assert_called_once_with('configfile')
self.assertIsNone(result)
patch('os.path.isdir', MagicMock(return_value=False))
def test_simple(self):
plugin = ReadOnlyTokenFile('configfile')
config = "testhost: remote_host:remote_port"
pyopen = mock_open(read_data=config)
with patch("websockify.token_plugins.open", pyopen, create=True):
result = plugin.lookup('testhost')
pyopen.assert_called_once_with('configfile')
self.assertIsNotNone(result)
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
patch('os.path.isdir', MagicMock(return_value=False))
def test_tabs(self):
plugin = ReadOnlyTokenFile('configfile')
config = "testhost:\tremote_host:remote_port"
pyopen = mock_open(read_data=config)
with patch("websockify.token_plugins.open", pyopen, create=True):
result = plugin.lookup('testhost')
pyopen.assert_called_once_with('configfile')
self.assertIsNotNone(result)
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
class JWSTokenTestCase(unittest.TestCase):
def test_asymmetric_jws_token_plugin(self):
plugin = JWTTokenApi("./tests/fixtures/public.pem")
key = jwt.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
result = plugin.lookup(jwt_token.serialize())
self.assertIsNotNone(result)
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
def test_asymmetric_jws_token_plugin_with_illigal_key_exception(self):
plugin = JWTTokenApi("wrong.pub")
key = jwt.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
result = plugin.lookup(jwt_token.serialize())
self.assertIsNone(result)
@patch('time.time')
def test_jwt_valid_time(self, mock_time):
plugin = JWTTokenApi("./tests/fixtures/public.pem")
key = jwt.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
jwt_token.make_signed_token(key)
mock_time.return_value = 150
result = plugin.lookup(jwt_token.serialize())
self.assertIsNotNone(result)
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
@patch('time.time')
def test_jwt_early_time(self, mock_time):
plugin = JWTTokenApi("./tests/fixtures/public.pem")
key = jwt.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
jwt_token.make_signed_token(key)
mock_time.return_value = 50
result = plugin.lookup(jwt_token.serialize())
self.assertIsNone(result)
@patch('time.time')
def test_jwt_late_time(self, mock_time):
plugin = JWTTokenApi("./tests/fixtures/public.pem")
key = jwt.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
jwt_token.make_signed_token(key)
mock_time.return_value = 250
result = plugin.lookup(jwt_token.serialize())
self.assertIsNone(result)
def test_symmetric_jws_token_plugin(self):
plugin = JWTTokenApi("./tests/fixtures/symmetric.key")
secret = open("./tests/fixtures/symmetric.key").read()
key = jwt.JWK()
key.import_key(kty="oct",k=secret)
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
result = plugin.lookup(jwt_token.serialize())
self.assertIsNotNone(result)
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
def test_symmetric_jws_token_plugin_with_illigal_key_exception(self):
plugin = JWTTokenApi("wrong_sauce")
secret = open("./tests/fixtures/symmetric.key").read()
key = jwt.JWK()
key.import_key(kty="oct",k=secret)
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
result = plugin.lookup(jwt_token.serialize())
self.assertIsNone(result)
def test_asymmetric_jwe_token_plugin(self):
plugin = JWTTokenApi("./tests/fixtures/private.pem")
private_key = jwt.JWK()
public_key = jwt.JWK()
private_key_data = open("./tests/fixtures/private.pem", "rb").read()
public_key_data = open("./tests/fixtures/public.pem", "rb").read()
private_key.import_from_pem(private_key_data)
public_key.import_from_pem(public_key_data)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(private_key)
jwe_token = jwt.JWT(header={"alg": "RSA1_5", "enc": "A256CBC-HS512"},
claims=jwt_token.serialize())
jwe_token.make_encrypted_token(public_key)
result = plugin.lookup(jwt_token.serialize())
self.assertIsNotNone(result)
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
+14 -123
View File
@@ -20,31 +20,18 @@ import sys
import unittest
import unittest
import socket
from io import StringIO
from io import BytesIO
from unittest.mock import patch, MagicMock
from mox3 import stubout
from websockify import websockifyserver
from websockify import websocketproxy
from websockify import token_plugins
from websockify import auth_plugins
if sys.version_info >= (2,7):
from jwcrypto import jwt
try:
from StringIO import StringIO
BytesIO = StringIO
except ImportError:
from io import StringIO
from io import BytesIO
class FakeSocket(object):
def __init__(self, data=''):
if isinstance(data, bytes):
self._data = data
else:
self._data = data.encode('latin_1')
def __init__(self, data=b''):
self._data = data
def recv(self, amt, flags=None):
res = self._data[0:amt]
@@ -74,16 +61,14 @@ class FakeServer(object):
class ProxyRequestHandlerTestCase(unittest.TestCase):
def setUp(self):
super(ProxyRequestHandlerTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.handler = websocketproxy.ProxyRequestHandler(
FakeSocket(''), "127.0.0.1", FakeServer())
FakeSocket(), "127.0.0.1", FakeServer())
self.handler.path = "https://localhost:6080/websockify?token=blah"
self.handler.headers = None
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
staticmethod(lambda *args, **kwargs: None))
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
def tearDown(self):
self.stubs.UnsetAll()
patch.stopall()
super(ProxyRequestHandlerTestCase, self).tearDown()
def test_get_target(self):
@@ -112,128 +97,34 @@ class ProxyRequestHandlerTestCase(unittest.TestCase):
def lookup(self, token):
return None
self.assertRaises(FakeServer.EClose, self.handler.get_target,
TestPlugin(None))
with self.assertRaises(FakeServer.EClose):
self.handler.get_target(TestPlugin(None))
@patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock())
def test_token_plugin(self):
class TestPlugin(token_plugins.BasePlugin):
def lookup(self, token):
return (self.source + token).split(',')
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
staticmethod(lambda *args, **kwargs: None))
self.handler.server.token_plugin = TestPlugin("somehost,")
self.handler.validate_connection()
self.assertEqual(self.handler.server.target_host, "somehost")
self.assertEqual(self.handler.server.target_port, "blah")
if sys.version_info >= (2,7):
def test_asymmetric_jws_token_plugin(self):
key = jwt.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwt_token.serialize())
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
staticmethod(lambda *args, **kwargs: None))
self.handler.server.token_plugin = token_plugins.JWTTokenApi("./tests/fixtures/public.pem")
self.handler.validate_connection()
self.assertEqual(self.handler.server.target_host, "remote_host")
self.assertEqual(self.handler.server.target_port, "remote_port")
def test_asymmetric_jws_token_plugin_with_illigal_key_exception(self):
key = jwt.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwt_token.serialize())
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
staticmethod(lambda *args, **kwargs: None))
self.handler.server.token_plugin = token_plugins.JWTTokenApi("wrong.pub")
self.assertRaises(self.handler.server.EClose,
self.handler.validate_connection)
def test_symmetric_jws_token_plugin(self):
secret = open("./tests/fixtures/symmetric.key").read()
key = jwt.JWK()
key.import_key(kty="oct",k=secret)
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwt_token.serialize())
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
staticmethod(lambda *args, **kwargs: None))
self.handler.server.token_plugin = token_plugins.JWTTokenApi("./tests/fixtures/symmetric.key")
self.handler.validate_connection()
self.assertEqual(self.handler.server.target_host, "remote_host")
self.assertEqual(self.handler.server.target_port, "remote_port")
def test_symmetric_jws_token_plugin_with_illigal_key_exception(self):
secret = open("./tests/fixtures/symmetric.key").read()
key = jwt.JWK()
key.import_key(kty="oct",k=secret)
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwt_token.serialize())
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
staticmethod(lambda *args, **kwargs: None))
self.handler.server.token_plugin = token_plugins.JWTTokenApi("wrong_sauce")
self.assertRaises(self.handler.server.EClose,
self.handler.validate_connection)
def test_asymmetric_jwe_token_plugin(self):
private_key = jwt.JWK()
public_key = jwt.JWK()
private_key_data = open("./tests/fixtures/private.pem", "rb").read()
public_key_data = open("./tests/fixtures/public.pem", "rb").read()
private_key.import_from_pem(private_key_data)
public_key.import_from_pem(public_key_data)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(private_key)
jwe_token = jwt.JWT(header={"alg": "RSA1_5", "enc": "A256CBC-HS512"},
claims=jwt_token.serialize())
jwe_token.make_encrypted_token(public_key)
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwe_token.serialize())
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
staticmethod(lambda *args, **kwargs: None))
self.handler.server.token_plugin = token_plugins.JWTTokenApi("./tests/fixtures/private.pem")
self.handler.validate_connection()
self.assertEqual(self.handler.server.target_host, "remote_host")
self.assertEqual(self.handler.server.target_port, "remote_port")
@patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock())
def test_auth_plugin(self):
class TestPlugin(auth_plugins.BasePlugin):
def authenticate(self, headers, target_host, target_port):
if target_host == self.source:
raise auth_plugins.AuthenticationError(response_msg="some_error")
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
staticmethod(lambda *args, **kwargs: None))
self.handler.server.auth_plugin = TestPlugin("somehost")
self.handler.server.target_host = "somehost"
self.handler.server.target_port = "someport"
self.assertRaises(auth_plugins.AuthenticationError,
self.handler.auth_connection)
with self.assertRaises(auth_plugins.AuthenticationError):
self.handler.auth_connection()
self.handler.server.target_host = "someotherhost"
self.handler.auth_connection()
+58 -114
View File
@@ -22,39 +22,26 @@ import select
import shutil
import socket
import ssl
from mox3 import stubout
from unittest.mock import patch, MagicMock, ANY
import sys
import tempfile
import unittest
import socket
import signal
from http.server import BaseHTTPRequestHandler
from io import StringIO
from io import BytesIO
from websockify import websockifyserver
try:
from BaseHTTPServer import BaseHTTPRequestHandler
except ImportError:
from http.server import BaseHTTPRequestHandler
try:
from StringIO import StringIO
BytesIO = StringIO
except ImportError:
from io import StringIO
from io import BytesIO
def raise_oserror(*args, **kwargs):
raise OSError('fake error')
class FakeSocket(object):
def __init__(self, data=''):
if isinstance(data, bytes):
self._data = data
else:
self._data = data.encode('latin_1')
def __init__(self, data=b''):
self._data = data
def recv(self, amt, flags=None):
res = self._data[0:amt]
@@ -73,22 +60,13 @@ class FakeSocket(object):
class WebSockifyRequestHandlerTestCase(unittest.TestCase):
def setUp(self):
super(WebSockifyRequestHandlerTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
# Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
lambda *args, **kwargs: None)
def fake_send_error(self, code, message=None, explain=None):
self.last_code = code
self.stubs.Set(BaseHTTPRequestHandler, 'send_error',
fake_send_error)
patch('os.chdir').start()
def tearDown(self):
"""Called automatically after each test."""
self.stubs.UnsetAll()
patch.stopall()
os.rmdir(self.tmpdir)
super(WebSockifyRequestHandlerTestCase, self).tearDown()
@@ -101,47 +79,36 @@ class WebSockifyRequestHandlerTestCase(unittest.TestCase):
record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1,
**kwargs)
def test_normal_get_with_only_upgrade_returns_error(self):
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
def test_normal_get_with_only_upgrade_returns_error(self, send_error):
server = self._get_server(web=None)
handler = websockifyserver.WebSockifyRequestHandler(
FakeSocket('GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
def fake_send_response(self, code, message=None):
self.last_code = code
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
fake_send_response)
FakeSocket(b'GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
handler.do_GET()
self.assertEqual(handler.last_code, 405)
send_error.assert_called_with(405, ANY)
def test_list_dir_with_file_only_returns_error(self):
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
def test_list_dir_with_file_only_returns_error(self, send_error):
server = self._get_server(file_only=True)
handler = websockifyserver.WebSockifyRequestHandler(
FakeSocket('GET / HTTP/1.1'), '127.0.0.1', server)
def fake_send_response(self, code, message=None):
self.last_code = code
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
fake_send_response)
FakeSocket(b'GET / HTTP/1.1'), '127.0.0.1', server)
handler.path = '/'
handler.do_GET()
self.assertEqual(handler.last_code, 404)
send_error.assert_called_with(404, ANY)
class WebSockifyServerTestCase(unittest.TestCase):
def setUp(self):
super(WebSockifyServerTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
# Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
patch('os.chdir').start()
def tearDown(self):
"""Called automatically after each test."""
self.stubs.UnsetAll()
patch.stopall()
os.rmdir(self.tmpdir)
super(WebSockifyServerTestCase, self).tearDown()
@@ -154,10 +121,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_daemonize_raises_error_while_closing_fds(self):
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: 0)
self.stubs.Set(signal, 'signal', lambda *args: None)
self.stubs.Set(os, 'setsid', lambda *args: None)
self.stubs.Set(os, 'close', raise_oserror)
patch('os.fork').start().return_value = 0
patch('signal.signal').start()
patch('os.setsid').start()
patch('os.close').start().side_effect = raise_oserror
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
def test_daemonize_ignores_ebadf_error_while_closing_fds(self):
@@ -165,11 +132,11 @@ class WebSockifyServerTestCase(unittest.TestCase):
raise OSError(errno.EBADF, 'fake error')
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: 0)
self.stubs.Set(os, 'setsid', lambda *args: None)
self.stubs.Set(signal, 'signal', lambda *args: None)
self.stubs.Set(os, 'close', raise_oserror_ebadf)
self.stubs.Set(os, 'open', raise_oserror)
patch('os.fork').start().return_value = 0
patch('signal.signal').start()
patch('os.setsid').start()
patch('os.close').start().side_effect = raise_oserror_ebadf
patch('os.open').start().side_effect = raise_oserror
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
def test_handshake_fails_on_not_ready(self):
@@ -178,7 +145,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None):
return ([], [], [])
self.stubs.Set(select, 'select', fake_select)
patch('select.select').start().side_effect = fake_select
self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
FakeSocket(), '127.0.0.1')
@@ -191,7 +158,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select)
patch('select.select').start().side_effect = fake_select
self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
sock, '127.0.0.1')
@@ -203,12 +170,12 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_handshake_ssl_only_without_ssl_raises_error(self):
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
sock = FakeSocket('some initial data')
sock = FakeSocket(b'some initial data')
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select)
patch('select.select').start().side_effect = fake_select
self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
sock, '127.0.0.1')
@@ -225,12 +192,12 @@ class WebSockifyServerTestCase(unittest.TestCase):
handler_class=FakeHandler, daemon=True,
ssl_only=0, idle_timeout=1)
sock = FakeSocket('some initial data')
sock = FakeSocket(b'some initial data')
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select)
patch('select.select').start().side_effect = fake_select
self.assertEqual(server.do_handshake(sock, '127.0.0.1'), sock)
self.assertTrue(FakeHandler.CALLED, True)
@@ -246,12 +213,12 @@ class WebSockifyServerTestCase(unittest.TestCase):
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1,
cert='afdsfasdafdsafdsafdsafdas')
sock = FakeSocket("\x16some ssl data")
sock = FakeSocket(b"\x16some ssl data")
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select)
patch('select.select').start().side_effect = fake_select
self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
sock, '127.0.0.1')
@@ -259,7 +226,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_do_handshake_ssl_error_eof_raises_close_error(self):
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
sock = FakeSocket("\x16some ssl data")
sock = FakeSocket(b"\x16some ssl data")
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
@@ -280,13 +247,8 @@ class WebSockifyServerTestCase(unittest.TestCase):
def wrap_socket(self, *args, **kwargs):
raise ssl.SSLError(ssl.SSL_ERROR_EOF)
self.stubs.Set(select, 'select', fake_select)
if (hasattr(ssl, 'create_default_context')):
# for recent versions of python
self.stubs.Set(ssl, 'create_default_context', fake_create_default_context)
else:
# for fallback for old versions of python
self.stubs.Set(ssl, 'wrap_socket', fake_wrap_socket)
patch('select.select').start().side_effect = fake_select
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
sock, '127.0.0.1')
@@ -300,7 +262,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
server = self._get_server(handler_class=FakeHandler, daemon=True,
idle_timeout=1, ssl_ciphers=test_ciphers)
sock = FakeSocket("\x16some ssl data")
sock = FakeSocket(b"\x16some ssl data")
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
@@ -321,16 +283,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
def set_ciphers(self, ciphers_to_set):
fake_create_default_context.CIPHERS = ciphers_to_set
self.stubs.Set(select, 'select', fake_select)
if (hasattr(ssl, 'create_default_context')):
# for recent versions of python
self.stubs.Set(ssl, 'create_default_context', fake_create_default_context)
server.do_handshake(sock, '127.0.0.1')
self.assertEqual(fake_create_default_context.CIPHERS, test_ciphers)
else:
# for fallback for old versions of python
# not supperted, nothing to test
pass
patch('select.select').start().side_effect = fake_select
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
server.do_handshake(sock, '127.0.0.1')
self.assertEqual(fake_create_default_context.CIPHERS, test_ciphers)
def test_do_handshake_ssl_sets_opions(self):
test_options = 0xCAFEBEEF
@@ -341,7 +297,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
server = self._get_server(handler_class=FakeHandler, daemon=True,
idle_timeout=1, ssl_options=test_options)
sock = FakeSocket("\x16some ssl data")
sock = FakeSocket(b"\x16some ssl data")
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
@@ -365,16 +321,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
fake_create_default_context.OPTIONS = val
options = property(get_options, set_options)
self.stubs.Set(select, 'select', fake_select)
if (hasattr(ssl, 'create_default_context')):
# for recent versions of python
self.stubs.Set(ssl, 'create_default_context', fake_create_default_context)
server.do_handshake(sock, '127.0.0.1')
self.assertEqual(fake_create_default_context.OPTIONS, test_options)
else:
# for fallback for old versions of python
# not supperted, nothing to test
pass
patch('select.select').start().side_effect = fake_select
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
server.do_handshake(sock, '127.0.0.1')
self.assertEqual(fake_create_default_context.OPTIONS, test_options)
def test_fallback_sigchld_handler(self):
# TODO(directxman12): implement this
@@ -387,11 +337,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None):
raise Exception("fake error")
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
lambda *args, **kwargs: sock)
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
patch('select.select').start().side_effect = fake_select
server.start_server()
def test_start_server_keyboardinterrupt(self):
@@ -401,11 +349,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None):
raise KeyboardInterrupt
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
lambda *args, **kwargs: sock)
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
patch('select.select').start().side_effect = fake_select
server.start_server()
def test_start_server_systemexit(self):
@@ -415,11 +361,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None):
sys.exit()
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
lambda *args, **kwargs: sock)
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
patch('select.select').start().side_effect = fake_select
server.start_server()
def test_socket_set_keepalive_options(self):
+1 -1
View File
@@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
envlist = py24,py26,py27,py33,py34
envlist = py34
[testenv]
commands = nosetests {posargs}
+6 -6
View File
@@ -1,4 +1,4 @@
class BasePlugin(object):
class BasePlugin():
def __init__(self, src=None):
self.source = src
@@ -15,7 +15,7 @@ class AuthenticationError(Exception):
if log_msg is None:
log_msg = response_msg
super(AuthenticationError, self).__init__('%s %s' % (self.code, log_msg))
super().__init__('%s %s' % (self.code, log_msg))
class InvalidOriginError(AuthenticationError):
@@ -23,13 +23,13 @@ class InvalidOriginError(AuthenticationError):
self.expected_origin = expected
self.actual_origin = actual
super(InvalidOriginError, self).__init__(
super().__init__(
response_msg='Invalid Origin',
log_msg="Invalid Origin Header: Expected one of "
"%s, got '%s'" % (expected, actual))
class BasicHTTPAuth(object):
class BasicHTTPAuth():
"""Verifies Basic Auth headers. Specify src as username:password"""
def __init__(self, src=None):
@@ -76,7 +76,7 @@ class BasicHTTPAuth(object):
raise AuthenticationError(response_code=401,
response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
class ExpectOrigin(object):
class ExpectOrigin():
def __init__(self, src=None):
if src is None:
self.source = []
@@ -88,7 +88,7 @@ class ExpectOrigin(object):
if origin is None or origin not in self.source:
raise InvalidOriginError(expected=self.source, actual=origin)
class ClientCertCNAuth(object):
class ClientCertCNAuth():
"""Verifies client by SSL certificate. Specify src as whitespace separated list of common names."""
def __init__(self, src=None):
+1 -1
View File
@@ -44,7 +44,7 @@ class WebsockifySysLogHandler(handlers.SysLogHandler):
self._legacy = True
self._head_fmt = self._legacy_head_fmt
handlers.SysLogHandler.__init__(self, address, facility, socktype)
super().__init__(address, facility, socktype)
def emit(self, record):
+88 -15
View File
@@ -1,8 +1,9 @@
from __future__ import print_function
import os
import sys
import time
import re
class BasePlugin(object):
class BasePlugin():
def __init__(self, src):
self.source = src
@@ -15,7 +16,7 @@ class ReadOnlyTokenFile(BasePlugin):
# token: host:port
# or a directory of such files
def __init__(self, *args, **kwargs):
super(ReadOnlyTokenFile, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self._targets = None
def _load_targets(self):
@@ -31,10 +32,10 @@ class ReadOnlyTokenFile(BasePlugin):
for line in [l.strip() for l in open(f).readlines()]:
if line and not line.startswith('#'):
try:
tok, target = line.split(': ')
tok, target = re.split(':\s', line)
self._targets[tok] = target.strip().rsplit(':', 1)
except ValueError:
print >>sys.stderr, "Syntax error in %s on line %d" % (self.source, index)
print("Syntax error in %s on line %d" % (self.source, index), file=sys.stderr)
index += 1
def lookup(self, token):
@@ -57,7 +58,7 @@ class TokenFile(ReadOnlyTokenFile):
def lookup(self, token):
self._load_targets()
return super(TokenFile, self).lookup(token)
return super().lookup(token)
class BaseTokenAPI(BasePlugin):
@@ -102,7 +103,7 @@ class JWTTokenApi(BasePlugin):
import json
key = jwt.JWK()
try:
with open(self.source, 'rb') as key_file:
key_data = key_file.read()
@@ -128,6 +129,18 @@ class JWTTokenApi(BasePlugin):
token = jwt.JWT(key=key, jwt=token.claims)
parsed = json.loads(token.claims)
if 'nbf' in parsed:
# Not Before is present, so we need to check it
if time.time() < parsed['nbf']:
print('Token can not be used yet!', file=sys.stderr)
return None
if 'exp' in parsed:
# Expiration time is present, so we need to check it
if time.time() > parsed['exp']:
print('Token has expired!', file=sys.stderr)
return None
return (parsed['host'], parsed['port'])
except Exception as e:
@@ -137,23 +150,83 @@ class JWTTokenApi(BasePlugin):
print("package jwcrypto not found, are you sure you've installed it correctly?", file=sys.stderr)
return None
class TokenRedis(object):
class TokenRedis():
"""
The TokenRedis plugin expects the format of the data in a form of json.
Prepare data with:
redis-cli set hello '{"host":"127.0.0.1:5000"}'
Verify with:
redis-cli --raw get hello
Spawn a test "server" using netcat
nc -l 5000 -v
Note: you have to install also the 'redis' and 'simplejson' modules
pip install redis simplejson
"""
def __init__(self, src):
self._server, self._port = src.split(":")
try:
# import those ahead of time so we provide error earlier
import redis
import simplejson
self._server, self._port = src.split(":")
print("TokenRedis backend initilized (%s:%s)" %
(self._server, self._port), file=sys.stderr)
except ValueError:
print("The provided --token-source='%s' is not in an expected format <host>:<port>" %
src, file=sys.stderr)
sys.exit()
except ImportError:
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
sys.exit()
def lookup(self, token):
try:
import redis
import simplejson
except ImportError as e:
except ImportError:
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
return None
sys.exit()
client = redis.Redis(host=self._server,port=self._port)
print("resolving token '%s'" % token, file=sys.stderr)
client = redis.Redis(host=self._server, port=self._port)
stuff = client.get(token)
if stuff is None:
return None
else:
combo = simplejson.loads(stuff.decode("utf-8"))
pair = combo["host"]
return pair.split(':')
responseStr = stuff.decode("utf-8")
print("response from redis : %s" % responseStr, file=sys.stderr)
combo = simplejson.loads(responseStr)
(host, port) = combo["host"].split(':')
print("host: %s, port: %s" % (host,port), file=sys.stderr)
return [host, port]
class UnixDomainSocketDirectory(BasePlugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._dir_path = os.path.abspath(self.source)
def lookup(self, token):
try:
import stat
if not os.path.isdir(self._dir_path):
return None
uds_path = os.path.abspath(os.path.join(self._dir_path, token))
if not uds_path.startswith(self._dir_path):
return None
if not os.path.exists(uds_path):
return None
if not stat.S_ISSOCK(os.stat(uds_path).st_mode):
return None
return [ 'unix_socket', uds_path ]
except Exception as e:
print("Error finding unix domain socket: %s" % str(e), file=sys.stderr)
return None
+110 -76
View File
@@ -22,6 +22,7 @@ import ssl
import struct
from base64 import b64encode
from hashlib import sha1
from urllib.parse import urlparse
try:
import numpy
@@ -30,25 +31,10 @@ except ImportError:
warnings.warn("no 'numpy' module, HyBi protocol will be slower")
numpy = None
# python 3.0 differences
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
# SSLWant*Error is 2.7.9+
try:
class WebSocketWantReadError(ssl.SSLWantReadError):
pass
class WebSocketWantWriteError(ssl.SSLWantWriteError):
pass
except AttributeError:
class WebSocketWantReadError(OSError):
def __init__(self):
OSError.__init__(self, errno.EWOULDBLOCK)
class WebSocketWantWriteError(OSError):
def __init__(self):
OSError.__init__(self, errno.EWOULDBLOCK)
class WebSocketWantReadError(ssl.SSLWantReadError):
pass
class WebSocketWantWriteError(ssl.SSLWantWriteError):
pass
class WebSocket(object):
"""WebSocket protocol socket like class.
@@ -87,11 +73,13 @@ class WebSocket(object):
self._state = "new"
self._partial_msg = ''.encode("ascii")
self._partial_msg = b''
self._recv_buffer = ''.encode("ascii")
self._recv_buffer = b''
self._recv_queue = []
self._send_buffer = ''.encode("ascii")
self._send_buffer = b''
self._previous_sendmsg = None
self._sent_close = False
self._received_close = False
@@ -164,9 +152,7 @@ class WebSocket(object):
self._key = ''
for i in range(16):
self._key += chr(random.randrange(256))
if sys.hexversion >= 0x3000000:
self._key = bytes(self._key, "latin-1")
self._key = b64encode(self._key).decode("ascii")
self._key = b64encode(self._key.encode("latin-1")).decode("ascii")
path = uri.path
if not path:
@@ -196,10 +182,10 @@ class WebSocket(object):
if not self._recv():
raise Exception("Socket closed unexpectedly")
if self._recv_buffer.find('\r\n\r\n'.encode("ascii")) == -1:
if self._recv_buffer.find(b'\r\n\r\n') == -1:
raise WebSocketWantReadError
(request, self._recv_buffer) = self._recv_buffer.split('\r\n'.encode("ascii"), 1)
(request, self._recv_buffer) = self._recv_buffer.split(b'\r\n', 1)
request = request.decode("latin-1")
words = request.split()
@@ -208,7 +194,7 @@ class WebSocket(object):
if words[1] != "101":
raise Exception("WebSocket request denied: %s" % " ".join(words[1:]))
(headers, self._recv_buffer) = self._recv_buffer.split('\r\n\r\n'.encode("ascii"), 1)
(headers, self._recv_buffer) = self._recv_buffer.split(b'\r\n\r\n', 1)
headers = headers.decode('latin-1') + '\r\n'
headers = email.message_from_string(headers)
@@ -254,8 +240,8 @@ class WebSocket(object):
the value "websocket" in such cases.
WebSocketWantWriteError can be raised if the response cannot be
sent right away. Repeated calls to accept() does not need to
retain the arguments.
sent right away. accept() must be called again once more space
is available using the same arguments.
"""
# This is a state machine in order to handle
@@ -419,8 +405,12 @@ class WebSocket(object):
data from other calls, or split it over multiple messages.
WebSocketWantWriteError can be raised if there is insufficient
space in the underlying socket.
space in the underlying socket. send() must be called again
once more space is available using the same arguments.
"""
if len(bytes) == 0:
return 0
return self.sendmsg(bytes)
def sendmsg(self, msg):
@@ -431,23 +421,81 @@ class WebSocket(object):
single WebSocket message.
WebSocketWantWriteError can be raised if there is insufficient
space in the underlying socket.
space in the underlying socket. sendmsg() must be called again
once more space is available using the same arguments.
"""
if not self._sent_close:
# Only called to flush?
if msg:
self._sendmsg(0x2, msg)
if not isinstance(msg, bytes):
raise TypeError
if self._sent_close:
return 0
if self._previous_sendmsg is not None:
if self._previous_sendmsg != msg:
raise ValueError
self._flush()
self._previous_sendmsg = None
return len(msg)
try:
self._sendmsg(0x2, msg)
except WebSocketWantWriteError:
self._previous_sendmsg = msg
raise
self._flush()
return len(msg)
def ping(self, data=''.encode('ascii')):
"""Write a ping message to the WebSocket."""
self._sendmsg(0x9, data)
def ping(self, data=b''):
"""Write a ping message to the WebSocket
def pong(self, data=''.encode('ascii')):
"""Write a pong message to the WebSocket."""
self._sendmsg(0xA, data)
WebSocketWantWriteError can be raised if there is insufficient
space in the underlying socket. ping() must be called again once
more space is available using the same arguments.
"""
if not isinstance(data, bytes):
raise TypeError
if self._previous_sendmsg is not None:
if self._previous_sendmsg != data:
raise ValueError
self._flush()
self._previous_sendmsg = None
return
try:
self._sendmsg(0x9, data)
except WebSocketWantWriteError:
self._previous_sendmsg = data
raise
def pong(self, data=b''):
"""Write a pong message to the WebSocket
WebSocketWantWriteError can be raised if there is insufficient
space in the underlying socket. pong() must be called again once
more space is available using the same arguments.
"""
if not isinstance(data, bytes):
raise TypeError
if self._previous_sendmsg is not None:
if self._previous_sendmsg != data:
raise ValueError
self._flush()
self._previous_sendmsg = None
return
try:
self._sendmsg(0xA, data)
except WebSocketWantWriteError:
self._previous_sendmsg = data
raise
def shutdown(self, how, code=1000, reason=None):
"""Gracefully terminate the WebSocket connection.
@@ -459,7 +507,9 @@ class WebSocket(object):
ignored.
WebSocketWantWriteError can be raised if there is insufficient
space in the underlying socket for the close message.
space in the underlying socket for the close message. shutdown()
must be called again once more space is available using the same
arguments.
The how argument is currently ignored.
"""
@@ -476,7 +526,7 @@ class WebSocket(object):
self._sent_close = True
msg = ''.encode('ascii')
msg = b''
if code is not None:
msg += struct.pack(">H", code)
if reason is not None:
@@ -491,7 +541,9 @@ class WebSocket(object):
a close message to the peer.
WebSocketWantWriteError can be raised if there is insufficient
space in the underlying socket for the close message.
space in the underlying socket for the close message. close()
must be called again once more space is available using the same
arguments.
"""
self.shutdown(socket.SHUT_RDWR, code, reason)
self._close()
@@ -503,16 +555,9 @@ class WebSocket(object):
while True:
try:
data = self.socket.recv(4096)
except (socket.error, OSError):
exc = sys.exc_info()[1]
if hasattr(exc, 'errno'):
err = exc.errno
else:
err = exc[0]
if err == errno.EWOULDBLOCK:
except OSError as exc:
if exc.errno == errno.EWOULDBLOCK:
raise WebSocketWantReadError
raise
if len(data) == 0:
@@ -569,7 +614,7 @@ class WebSocket(object):
if frame["fin"]:
msg = self._partial_msg
self._partial_msg = ''.decode("ascii")
self._partial_msg = b''
return msg
elif frame["opcode"] == 0x1:
self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Text frames are not supported")
@@ -644,16 +689,9 @@ class WebSocket(object):
try:
sent = self.socket.send(self._send_buffer)
except (socket.error, OSError):
exc = sys.exc_info()[1]
if hasattr(exc, 'errno'):
err = exc.errno
else:
err = exc[0]
if err == errno.EWOULDBLOCK:
except OSError as exc:
if exc.errno == errno.EWOULDBLOCK:
raise WebSocketWantWriteError
raise
self._send_buffer = self._send_buffer[sent:]
@@ -679,11 +717,9 @@ class WebSocket(object):
def _sendmsg(self, opcode, msg):
# Sends a standard data message
if self.client:
mask = ''
mask = b''
for i in range(4):
mask += chr(random.randrange(256))
if sys.hexversion >= 0x3000000:
mask = bytes(mask, "latin-1")
mask += random.randrange(256)
frame = self._encode_hybi(opcode, msg, mask)
else:
frame = self._encode_hybi(opcode, msg)
@@ -705,7 +741,7 @@ class WebSocket(object):
plen = len(buf)
pstart = 0
pend = plen
b = c = ''.encode('ascii')
b = c = b''
if plen >= 4:
dtype=numpy.dtype('<u4')
if sys.byteorder == 'big':
@@ -713,7 +749,7 @@ class WebSocket(object):
mask = numpy.frombuffer(mask, dtype, count=1)
data = numpy.frombuffer(buf, dtype, count=int(plen / 4))
#b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tostring()
b = numpy.bitwise_xor(data, mask).tobytes()
if plen % 4:
dtype=numpy.dtype('B')
@@ -722,17 +758,15 @@ class WebSocket(object):
mask = numpy.frombuffer(mask, dtype, count=(plen % 4))
data = numpy.frombuffer(buf, dtype,
offset=plen - (plen % 4), count=(plen % 4))
c = numpy.bitwise_xor(data, mask).tostring()
c = numpy.bitwise_xor(data, mask).tobytes()
return b + c
else:
# Slower fallback
if sys.hexversion < 0x3000000:
mask = [ ord(c) for c in mask ]
data = array.array('B')
data.fromstring(buf)
data.frombytes(buf)
for i in range(len(data)):
data[i] ^= mask[i % 4]
return data.tostring()
return data.tobytes()
def _encode_hybi(self, opcode, buf, mask_key=None, fin=True):
""" Encode a HyBi style WebSocket frame.
+19 -50
View File
@@ -12,24 +12,13 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl
try:
from socketserver import ThreadingMixIn
except ImportError:
from SocketServer import ThreadingMixIn
try:
from http.server import HTTPServer
except ImportError:
from BaseHTTPServer import HTTPServer
from socketserver import ThreadingMixIn
from http.server import HTTPServer
import select
from websockify import websockifyserver
from websockify import auth_plugins as auth
try:
from urllib.parse import parse_qs, urlparse
except ImportError:
from cgi import parse_qs
from urlparse import urlparse
from urllib.parse import parse_qs, urlparse
class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler):
@@ -118,9 +107,9 @@ Traffic Legend:
connect=True,
use_ssl=self.server.ssl_target,
unix_socket=self.server.unix_target)
except Exception:
self.log_message("Failed to connect to %s:%s",
self.server.target_host, self.server.target_port)
except Exception as e:
self.log_message("Failed to connect to %s:%s: %s",
self.server.target_host, self.server.target_port, e)
raise self.CClose(1011, "Failed to connect to downstream server")
self.request.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
@@ -293,6 +282,7 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
wsdir = os.path.dirname(sys.argv[0])
rebinder_path = [os.path.join(wsdir, "..", "lib"),
os.path.join(wsdir, "..", "lib", "websockify"),
os.path.join(wsdir, ".."),
wsdir]
self.rebinder = None
@@ -318,7 +308,7 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
websockifyserver.WebSockifyServer.__init__(self, RequestHandlerClass, *args, **kwargs)
super().__init__(RequestHandlerClass, *args, **kwargs)
def run_wrap_cmd(self):
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
@@ -394,35 +384,15 @@ def _subprocess_setup():
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
try :
# First try SSL options for Python 3.4 and above
SSL_OPTIONS = {
'default': ssl.OP_ALL,
'tlsv1_1': ssl.PROTOCOL_TLS | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1,
'tlsv1_2': ssl.PROTOCOL_TLS | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1,
'tlsv1_3': ssl.PROTOCOL_TLS | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2,
}
except AttributeError:
try:
# Python 3.3 uses a different scheme for SSL options
# tlsv1_3 is not supported on older Python versions
SSL_OPTIONS = {
'default': ssl.OP_ALL,
'tlsv1_1': ssl.PROTOCOL_TLSv1 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1,
'tlsv1_2': ssl.PROTOCOL_TLSv1 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1,
}
except AttributeError:
# Python 2.6 does not support TLS v1.2, and uses a different scheme
# for SSL options
SSL_OPTIONS = {
'default': ssl.PROTOCOL_SSLv23,
'tlsv1_1': ssl.PROTOCOL_TLSv1,
}
SSL_OPTIONS = {
'default': ssl.OP_ALL,
'tlsv1_1': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1,
'tlsv1_2': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1,
'tlsv1_3': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2,
}
def select_ssl_version(version):
"""Returns SSL options for the most secure TSL version available on this
@@ -768,14 +738,13 @@ class LibProxyServer(ThreadingMixIn, HTTPServer):
if web:
os.chdir(web)
HTTPServer.__init__(self, (listen_host, listen_port),
RequestHandlerClass)
super().__init__((listen_host, listen_port), RequestHandlerClass)
def process_request(self, request, client_address):
"""Override process_request to implement a counter"""
self.handler_id += 1
ThreadingMixIn.process_request(self, request, client_address)
super().process_request(request, client_address)
if __name__ == '__main__':
+2 -12
View File
@@ -8,12 +8,7 @@ Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
'''
import sys
# python 3.0 differences
try:
from http.server import BaseHTTPRequestHandler, HTTPServer
except ImportError:
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from http.server import BaseHTTPRequestHandler, HTTPServer
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
@@ -42,12 +37,7 @@ class WebSocketRequestHandlerMixIn:
self._real_do_GET = self.do_GET
self.do_GET = self._websocket_do_GET
try:
# super() only works for new style classes
if issubclass(WebSocketRequestHandlerMixIn, object):
super(WebSocketRequestHandlerMixIn, self).handle_one_request()
else:
# Assume handle_one_request() hasn't been overriden
BaseHTTPRequestHandler.handle_one_request(self)
super().handle_one_request()
finally:
self.do_GET = self._real_do_GET
+29 -62
View File
@@ -14,19 +14,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
import os, sys, time, errno, signal, socket, select, logging
import multiprocessing
# Imports that vary by python version
# python 3.0 differences
if sys.hexversion > 0x3000000:
s2b = lambda s: s.encode('latin_1')
else:
s2b = lambda s: s # No-op
try:
from http.server import SimpleHTTPRequestHandler
except ImportError:
from SimpleHTTPServer import SimpleHTTPRequestHandler
from http.server import SimpleHTTPRequestHandler
# Degraded functionality if these imports are missing
for mod, msg in [('ssl', 'TLS/SSL/wss is disabled'),
@@ -46,7 +34,7 @@ from websockify.websocketserver import WebSocketRequestHandlerMixIn
class CompatibleWebSocket(WebSocket):
def select_subprotocol(self, protocols):
# Handle old websockify clients that still specifiy a sub-protocol
# Handle old websockify clients that still specify a sub-protocol
if 'binary' in protocols:
return 'binary'
else:
@@ -96,7 +84,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
if self.logger is None:
self.logger = WebSockifyServer.get_logger()
SimpleHTTPRequestHandler.__init__(self, req, addr, server)
super().__init__(req, addr, server)
def log_message(self, format, *args):
self.logger.info("%s - - [%s] %s" % (self.client_address[0], self.log_date_time_string(), format % args))
@@ -146,20 +134,14 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
self.rec.write("'{{{0}{{{1}',\n".format(tdelta, bufstr))
self.send_parts.append(buf)
# Flush any previously queued data
try:
self.request.sendmsg('')
except WebSocketWantWriteError:
return True
while self.send_parts:
# Send pending frames
buf = self.send_parts.pop(0)
try:
self.request.sendmsg(buf)
self.request.sendmsg(self.send_parts[0])
except WebSocketWantWriteError:
self.print_traffic("<.")
return True
self.send_parts.pop(0)
self.print_traffic("<")
return False
@@ -218,7 +200,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
self.validate_connection()
self.auth_connection()
WebSocketRequestHandlerMixIn.handle_upgrade(self)
super().handle_upgrade()
def handle_websocket(self):
# Indicate to server that a Websocket upgrade was done
@@ -270,13 +252,13 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
if self.only_upgrade:
self.send_error(405, "Method Not Allowed")
else:
SimpleHTTPRequestHandler.do_GET(self)
super().do_GET()
def list_directory(self, path):
if self.file_only:
self.send_error(404, "No such file")
else:
return SimpleHTTPRequestHandler.list_directory(self, path)
return super().list_directory(path)
def new_websocket_client(self):
""" Do something with a WebSockets client connection. """
@@ -297,13 +279,13 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
if self.only_upgrade:
self.send_error(405, "Method Not Allowed")
else:
SimpleHTTPRequestHandler.do_HEAD(self)
super().do_HEAD()
def finish(self):
if self.rec:
self.rec.write("'EOF'];\n")
self.rec.close()
SimpleHTTPRequestHandler.finish(self)
super().finish()
def handle(self):
# When using run_once, we have a single process, so
@@ -312,14 +294,14 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
if self.run_once:
self.handle_one_request()
else:
SimpleHTTPRequestHandler.handle(self)
super().handle()
def log_request(self, code='-', size='-'):
if self.verbose:
SimpleHTTPRequestHandler.log_request(self, code, size)
super().log_request(code, size)
class WebSockifyServer(object):
class WebSockifyServer():
"""
WebSockets server class.
As an alternative, the standard library SocketServer can be used
@@ -559,7 +541,7 @@ class WebSockifyServer(object):
if not handshake:
raise self.EClose("")
elif handshake[0] in ("\x16", "\x80", 22, 128):
elif handshake[0] in (22, 128):
# SSL wrap the connection
if not ssl:
raise self.EClose("SSL connection but no 'ssl' module")
@@ -568,32 +550,21 @@ class WebSockifyServer(object):
% self.cert)
retsock = None
try:
if (hasattr(ssl, 'create_default_context')
and callable(ssl.create_default_context)):
# create new-style SSL wrapping for extended features
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
if self.ssl_ciphers is not None:
context.set_ciphers(self.ssl_ciphers)
context.options = self.ssl_options
context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.key_password)
if self.verify_client:
context.verify_mode = ssl.CERT_REQUIRED
if self.cafile:
context.load_verify_locations(cafile=self.cafile)
else:
context.set_default_verify_paths()
retsock = context.wrap_socket(
sock,
server_side=True)
else:
if self.verify_client:
raise self.EClose("Client certificate verification requested, but this Python is too old.")
# new-style SSL wrapping is not needed, using to old style
retsock = ssl.wrap_socket(
sock,
server_side=True,
certfile=self.cert,
keyfile=self.key)
# create new-style SSL wrapping for extended features
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
if self.ssl_ciphers is not None:
context.set_ciphers(self.ssl_ciphers)
context.options = self.ssl_options
context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.key_password)
if self.verify_client:
context.verify_mode = ssl.CERT_REQUIRED
if self.cafile:
context.load_verify_locations(cafile=self.cafile)
else:
context.set_default_verify_paths()
retsock = context.wrap_socket(
sock,
server_side=True)
except ssl.SSLError:
_, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF:
@@ -729,10 +700,6 @@ class WebSockifyServer(object):
if self.listen_fd != None:
lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM)
if sys.hexversion < 0x3000000:
# For python 2 we have to wrap the "raw" socket into a socket object,
# otherwise ssl wrap_socket doesn't work.
lsock = socket.socket(_sock=lsock)
else:
lsock = self.socket(self.listen_host, self.listen_port, False,
self.prefer_ipv6,