diff options
author | morpheus65535 <[email protected]> | 2022-01-23 23:07:52 -0500 |
---|---|---|
committer | morpheus65535 <[email protected]> | 2022-01-23 23:07:52 -0500 |
commit | 0c3c5a02a75bc61b6bf6e303de20e11741d2afac (patch) | |
tree | 30ae1d524ffe5d54172b7a4a8445d90c3461e659 /libs/websocket | |
parent | 36bf0d219d0432c20e6314e0ce752b36f4d88e3c (diff) | |
download | bazarr-0c3c5a02a75bc61b6bf6e303de20e11741d2afac.tar.gz bazarr-0c3c5a02a75bc61b6bf6e303de20e11741d2afac.zip |
Upgraded vendored Python dependencies to the latest versions and removed the unused dependencies.v1.0.3-beta.16
Diffstat (limited to 'libs/websocket')
-rw-r--r-- | libs/websocket/__init__.py | 26 | ||||
-rw-r--r-- | libs/websocket/_abnf.py | 77 | ||||
-rw-r--r-- | libs/websocket/_app.py | 138 | ||||
-rw-r--r-- | libs/websocket/_cookiejar.py | 26 | ||||
-rw-r--r-- | libs/websocket/_core.py | 235 | ||||
-rw-r--r-- | libs/websocket/_exceptions.py | 26 | ||||
-rw-r--r-- | libs/websocket/_handshake.py | 26 | ||||
-rw-r--r-- | libs/websocket/_http.py | 192 | ||||
-rw-r--r-- | libs/websocket/_logging.py | 24 | ||||
-rw-r--r-- | libs/websocket/_socket.py | 28 | ||||
-rw-r--r-- | libs/websocket/_ssl_compat.py | 29 | ||||
-rw-r--r-- | libs/websocket/_url.py | 62 | ||||
-rw-r--r-- | libs/websocket/_utils.py | 26 | ||||
-rwxr-xr-x | libs/websocket/_wsdump.py | 231 | ||||
-rw-r--r-- | libs/websocket/tests/echo-server.py | 21 | ||||
-rw-r--r-- | libs/websocket/tests/test_abnf.py | 29 | ||||
-rw-r--r-- | libs/websocket/tests/test_app.py | 43 | ||||
-rw-r--r-- | libs/websocket/tests/test_cookiejar.py | 29 | ||||
-rw-r--r-- | libs/websocket/tests/test_http.py | 94 | ||||
-rw-r--r-- | libs/websocket/tests/test_url.py | 30 | ||||
-rw-r--r-- | libs/websocket/tests/test_websocket.py | 71 |
21 files changed, 870 insertions, 593 deletions
diff --git a/libs/websocket/__init__.py b/libs/websocket/__init__.py index 38da7b30d..05aae2bd7 100644 --- a/libs/websocket/__init__.py +++ b/libs/websocket/__init__.py @@ -1,22 +1,20 @@ """ +__init__.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ from ._abnf import * from ._app import WebSocketApp @@ -25,4 +23,4 @@ from ._exceptions import * from ._logging import * from ._socket import * -__version__ = "1.0.0" +__version__ = "1.2.3" diff --git a/libs/websocket/_abnf.py b/libs/websocket/_abnf.py index 554f3b085..e9909ff62 100644 --- a/libs/websocket/_abnf.py +++ b/libs/websocket/_abnf.py @@ -3,52 +3,51 @@ """ """ +_abnf.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import array import os import struct +import sys from ._exceptions import * from ._utils import validate_utf8 from threading import Lock try: - import numpy -except ImportError: - numpy = None + # If wsaccel is available, use compiled routines to mask data. + # wsaccel only provides around a 10% speed boost compared + # to the websocket-client _mask() implementation. + # Note that wsaccel is unmaintained. + from wsaccel.xormask import XorMaskerSimple -try: - # If wsaccel is available we use compiled routines to mask data. - if not numpy: - from wsaccel.xormask import XorMaskerSimple + def _mask(_m, _d): + return XorMaskerSimple(_m).process(_d) - def _mask(_m, _d): - return XorMaskerSimple(_m).process(_d) except ImportError: - # wsaccel is not available, we rely on python implementations. - def _mask(_m, _d): - for i in range(len(_d)): - _d[i] ^= _m[i % 4] + # wsaccel is not available, use websocket-client _mask() + native_byteorder = sys.byteorder - return _d.tobytes() + def _mask(mask_value, data_value): + datalen = len(data_value) + data_value = int.from_bytes(data_value, native_byteorder) + mask_value = int.from_bytes(mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder) + return (data_value ^ mask_value).to_bytes(datalen, native_byteorder) __all__ = [ @@ -97,7 +96,7 @@ VALID_CLOSE_STATUS = ( ) -class ABNF(object): +class ABNF: """ ABNF frame class. See http://tools.ietf.org/html/rfc5234 @@ -266,22 +265,10 @@ class ABNF(object): if isinstance(data, str): data = data.encode('latin-1') - if numpy: - origlen = len(data) - _mask_key = mask_key[3] << 24 | mask_key[2] << 16 | mask_key[1] << 8 | mask_key[0] - - # We need data to be a multiple of four... - data += b' ' * (4 - (len(data) % 4)) - a = numpy.frombuffer(data, dtype="uint32") - masked = numpy.bitwise_xor(a, [_mask_key]).astype("uint32") - if len(data) > origlen: - return masked.tobytes()[:origlen] - return masked.tobytes() - else: - return _mask(array.array("B", mask_key), array.array("B", data)) + return _mask(array.array("B", mask_key), array.array("B", data)) -class frame_buffer(object): +class frame_buffer: _HEADER_MASK_INDEX = 5 _HEADER_LENGTH_INDEX = 6 @@ -374,7 +361,7 @@ class frame_buffer(object): return frame def recv_strict(self, bufsize): - shortage = bufsize - sum(len(x) for x in self.recv_buffer) + shortage = bufsize - sum(map(len, self.recv_buffer)) while shortage > 0: # Limit buffer size that we pass to socket.recv() to avoid # fragmenting the heap -- the number of bytes recv() actually @@ -386,7 +373,7 @@ class frame_buffer(object): self.recv_buffer.append(bytes_) shortage -= len(bytes_) - unified = bytes("", 'utf-8').join(self.recv_buffer) + unified = b"".join(self.recv_buffer) if shortage == 0: self.recv_buffer = [] @@ -396,7 +383,7 @@ class frame_buffer(object): return unified[:bufsize] -class continuous_frame(object): +class continuous_frame: def __init__(self, fire_cont_frame, skip_utf8_validation): self.fire_cont_frame = fire_cont_frame diff --git a/libs/websocket/_app.py b/libs/websocket/_app.py index 7dbacb3c6..1afd3d20a 100644 --- a/libs/websocket/_app.py +++ b/libs/websocket/_app.py @@ -3,24 +3,22 @@ """ """ +_app.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import selectors import sys @@ -88,7 +86,7 @@ class SSLDispatcher: return r[0][0] -class WebSocketApp(object): +class WebSocketApp: """ Higher level of APIs are provided. The interface is like JavaScript WebSocket object. """ @@ -105,52 +103,56 @@ class WebSocketApp(object): Parameters ---------- - url: <type> - websocket url. + url: str + Websocket url. header: list or dict - custom header for websocket handshake. - on_open: <type> - callable object which is called at opening websocket. - this function has one argument. The argument is this class object. - on_message: <type> - callable object which is called when received data. + Custom header for websocket handshake. + on_open: function + Callback object which is called at opening websocket. + on_open has one argument. + The 1st argument is this class object. + on_message: function + Callback object which is called when received data. on_message has 2 arguments. The 1st argument is this class object. - The 2nd argument is utf-8 string which we get from the server. - on_error: <type> - callable object which is called when we get error. + The 2nd argument is utf-8 data received from the server. + on_error: function + Callback object which is called when we get error. on_error has 2 arguments. The 1st argument is this class object. The 2nd argument is exception object. - on_close: <type> - callable object which is called when closed the connection. - this function has one argument. The argument is this class object. - on_cont_message: <type> - callback object which is called when receive continued - frame data. + on_close: function + Callback object which is called when connection is closed. + on_close has 3 arguments. + The 1st argument is this class object. + The 2nd argument is close_status_code. + The 3rd argument is close_msg. + on_cont_message: function + Callback object which is called when a continuation + frame is received. on_cont_message has 3 arguments. The 1st argument is this class object. The 2nd argument is utf-8 string which we get from the server. The 3rd argument is continue flag. if 0, the data continue to next frame data - on_data: <type> - callback object which is called when a message received. + on_data: function + Callback object which is called when a message received. This is called before on_message or on_cont_message, and then on_message or on_cont_message is called. on_data has 4 argument. The 1st argument is this class object. The 2nd argument is utf-8 string which we get from the server. The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came. - The 4th argument is continue flag. if 0, the data continue - keep_running: <type> - this parameter is obsolete and ignored. - get_mask_key: func - a callable to produce new mask keys, - see the WebSocket.set_mask_key's docstring for more information + The 4th argument is continue flag. If 0, the data continue + keep_running: bool + This parameter is obsolete and ignored. + get_mask_key: function + A callable function to get new mask keys, see the + WebSocket.set_mask_key's docstring for more information. cookie: str - cookie value. - subprotocols: <type> - array of available sub protocols. default is None. + Cookie value. + subprotocols: list + List of available sub protocols. Default is None. """ self.url = url self.header = header if header is not None else [] @@ -177,11 +179,11 @@ class WebSocketApp(object): Parameters ---------- - data: <type> + data: str Message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode. - opcode: <type> - Operation code of data. default is OPCODE_TEXT. + opcode: int + Operation code of data. Default is OPCODE_TEXT. """ if not self.sock or self.sock.send(data, opcode) == 0: @@ -223,32 +225,32 @@ class WebSocketApp(object): Parameters ---------- sockopt: tuple - values for socket.setsockopt. + Values for socket.setsockopt. sockopt must be tuple and each element is argument of sock.setsockopt. sslopt: dict - optional dict object for ssl socket option. + Optional dict object for ssl socket option. ping_interval: int or float - automatically send "ping" command - every specified period (in seconds) - if set to 0, not send automatically. + Automatically send "ping" command + every specified period (in seconds). + If set to 0, no ping is sent periodically. ping_timeout: int or float - timeout (in seconds) if the pong message is not received. + Timeout (in seconds) if the pong message is not received. ping_payload: str - payload message to send with each ping. - http_proxy_host: <type> - http proxy host name. - http_proxy_port: <type> - http proxy port. If not set, set to 80. - http_no_proxy: <type> - host names, which doesn't use proxy. + Payload message to send with each ping. + http_proxy_host: str + HTTP proxy host name. + http_proxy_port: int or str + HTTP proxy port. If not set, set to 80. + http_no_proxy: list + Whitelisted host names that don't use the proxy. skip_utf8_validation: bool skip utf8 validation. host: str update host header. origin: str update origin header. - dispatcher: <type> + dispatcher: Dispatcher object customize reading data from socket. suppress_origin: bool suppress outputting origin header. @@ -280,8 +282,11 @@ class WebSocketApp(object): """ Tears down the connection. - If close_frame is set, we will invoke the on_close handler with the - statusCode and reason from there. + Parameters + ---------- + close_frame: ABNF frame + If close_frame is set, the on_close handler is invoked + with the statusCode and reason from the provided frame. """ if thread and thread.is_alive(): @@ -292,15 +297,17 @@ class WebSocketApp(object): self.sock.close() close_status_code, close_reason = self._get_close_args( close_frame if close_frame else None) - self._callback(self.on_close, close_status_code, close_reason) self.sock = None + # Finally call the callback AFTER all teardown is complete + self._callback(self.on_close, close_status_code, close_reason) + try: self.sock = WebSocket( self.get_mask_key, sockopt=sockopt, sslopt=sslopt, fire_cont_frame=self.on_cont_message is not None, skip_utf8_validation=skip_utf8_validation, - enable_multithread=True if ping_interval else False) + enable_multithread=True) self.sock.settimeout(getdefaulttimeout()) self.sock.connect( self.url, header=self.header, cookie=self.cookie, @@ -401,6 +408,5 @@ class WebSocketApp(object): except Exception as e: _logging.error("error from callback {}: {}".format(callback, e)) - if _logging.isEnabledForDebug(): - _, _, tb = sys.exc_info() - traceback.print_tb(tb) + if self.on_error: + self.on_error(self, e) diff --git a/libs/websocket/_cookiejar.py b/libs/websocket/_cookiejar.py index 0359d0355..878538348 100644 --- a/libs/websocket/_cookiejar.py +++ b/libs/websocket/_cookiejar.py @@ -3,29 +3,27 @@ """ """ +_cookiejar.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import http.cookies -class SimpleCookieJar(object): +class SimpleCookieJar: def __init__(self): self.jar = dict() diff --git a/libs/websocket/_core.py b/libs/websocket/_core.py index 1e36d90b2..e26c8b115 100644 --- a/libs/websocket/_core.py +++ b/libs/websocket/_core.py @@ -5,24 +5,22 @@ WebSocket Python client """ """ +_core.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +Copyright 2021 engn33r - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. +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 - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import socket import struct @@ -42,7 +40,7 @@ from ._utils import * __all__ = ['WebSocket', 'create_connection'] -class WebSocket(object): +class WebSocket: """ Low level WebSocket interface. @@ -62,30 +60,31 @@ class WebSocket(object): Parameters ---------- get_mask_key: func - a callable to produce new mask keys, see the set_mask_key - function's docstring for more details + A callable function to get new mask keys, see the + WebSocket.set_mask_key's docstring for more information. sockopt: tuple - values for socket.setsockopt. + Values for socket.setsockopt. sockopt must be tuple and each element is argument of sock.setsockopt. sslopt: dict - optional dict object for ssl socket option. + Optional dict object for ssl socket options. See FAQ for details. fire_cont_frame: bool - fire recv event for each cont frame. default is False + Fire recv event for each cont frame. Default is False. enable_multithread: bool - if set to True, lock send method. + If set to True, lock send method. skip_utf8_validation: bool - skip utf8 validation. + Skip utf8 validation. """ def __init__(self, get_mask_key=None, sockopt=None, sslopt=None, - fire_cont_frame=False, enable_multithread=False, + fire_cont_frame=False, enable_multithread=True, skip_utf8_validation=False, **_): """ Initialize WebSocket object. Parameters ---------- - sslopt: specify ssl certification verification options + sslopt: dict + Optional dict object for ssl socket options. See FAQ for details. """ self.sock_opt = sock_opt(sockopt, sslopt) self.handshake_response = None @@ -194,7 +193,10 @@ class WebSocket(object): return None def is_ssl(self): - return isinstance(self.sock, ssl.SSLSocket) + try: + return isinstance(self.sock, ssl.SSLSocket) + except: + return False headers = property(getheaders) @@ -210,40 +212,38 @@ class WebSocket(object): ... header=["User-Agent: MyProgram", ... "x-custom: header"]) - timeout: <type> - socket timeout time. This value is an integer or float. - if you set None for this value, it means "use default_timeout value" - Parameters ---------- - options: - - header: list or dict - custom http header list or dict. - - cookie: str - cookie value. - - origin: str - custom origin url. - - connection: str - custom connection header value. - default value "Upgrade" set in _handshake.py - - suppress_origin: bool - suppress outputting origin header. - - host: str - custom host header string. - - http_proxy_host: <type> - http proxy host name. - - http_proxy_port: <type> - http proxy port. If not set, set to 80. - - http_no_proxy: <type> - host names, which doesn't use proxy. - - http_proxy_auth: <type> - http proxy auth information. tuple of username and password. default is None - - redirect_limit: <type> - number of redirects to follow. - - subprotocols: <type> - array of available sub protocols. default is None. - - socket: <type> - pre-initialized stream socket. + header: list or dict + Custom http header list or dict. + cookie: str + Cookie value. + origin: str + Custom origin url. + connection: str + Custom connection header value. + Default value "Upgrade" set in _handshake.py + suppress_origin: bool + Suppress outputting origin header. + host: str + Custom host header string. + timeout: int or float + Socket timeout time. This value is an integer or float. + If you set None for this value, it means "use default_timeout value" + http_proxy_host: str + HTTP proxy host name. + http_proxy_port: str or int + HTTP proxy port. Default is 80. + http_no_proxy: list + Whitelisted host names that don't use the proxy. + http_proxy_auth: tuple + HTTP proxy auth information. Tuple of username and password. Default is None. + redirect_limit: int + Number of redirects to follow. + subprotocols: list + List of available subprotocols. Default is None. + socket: socket + Pre-initialized stream socket. """ self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout) self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), @@ -271,12 +271,12 @@ class WebSocket(object): Parameters ---------- - payload: str - Payload must be utf-8 string or unicode, - if the opcode is OPCODE_TEXT. - Otherwise, it must be string(byte array) - opcode: int - operation code to send. Please see OPCODE_XXX. + payload: str + Payload must be utf-8 string or unicode, + If the opcode is OPCODE_TEXT. + Otherwise, it must be string(byte array). + opcode: int + Operation code (opcode) to send. """ frame = ABNF.create_frame(payload, opcode) @@ -314,6 +314,14 @@ class WebSocket(object): return length def send_binary(self, payload): + """ + Send a binary message (OPCODE_BINARY). + + Parameters + ---------- + payload: bytes + payload of message to send. + """ return self.send(payload, ABNF.OPCODE_BINARY) def ping(self, payload=""): @@ -381,6 +389,8 @@ class WebSocket(object): """ Receive data with operation code. + If a valid ping message is received, a pong response is sent. + Parameters ---------- control_frame: bool @@ -434,34 +444,34 @@ class WebSocket(object): """ return self.frame_buffer.recv_frame() - def send_close(self, status=STATUS_NORMAL, reason=bytes('', encoding='utf-8')): + def send_close(self, status=STATUS_NORMAL, reason=b""): """ Send close data to the server. Parameters ---------- - status: <type> - status code to send. see STATUS_XXX. + status: int + Status code to send. See STATUS_XXX. reason: str or bytes - the reason to close. This must be string or bytes. + The reason to close. This must be string or UTF-8 bytes. """ if status < 0 or status >= ABNF.LENGTH_16: raise ValueError("code is invalid range") self.connected = False self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) - def close(self, status=STATUS_NORMAL, reason=bytes('', encoding='utf-8'), timeout=3): + def close(self, status=STATUS_NORMAL, reason=b"", timeout=3): """ Close Websocket object Parameters ---------- status: int - status code to send. see STATUS_XXX. + Status code to send. See STATUS_XXX. reason: bytes - the reason to close. + The reason to close in UTF-8. timeout: int or float - timeout until receive a close frame. + Timeout until receive a close frame. If None, it will wait forever until receive a close frame. """ if self.connected: @@ -490,10 +500,8 @@ class WebSocket(object): break self.sock.settimeout(sock_timeout) self.sock.shutdown(socket.SHUT_RDWR) - except OSError: # This happens often on Mac - pass except: - raise + pass self.shutdown() @@ -544,52 +552,51 @@ def create_connection(url, timeout=None, class_=WebSocket, **options): Parameters ---------- + class_: class + class to instantiate when creating the connection. It has to implement + settimeout and connect. It's __init__ should be compatible with + WebSocket.__init__, i.e. accept all of it's kwargs. + header: list or dict + custom http header list or dict. + cookie: str + Cookie value. + origin: str + custom origin url. + suppress_origin: bool + suppress outputting origin header. + host: str + custom host header string. timeout: int or float - socket timeout time. This value could be either float/integer. - if you set None for this value, - it means "use default_timeout value" - class_: <type> - class to instantiate when creating the connection. It has to implement - settimeout and connect. It's __init__ should be compatible with - WebSocket.__init__, i.e. accept all of it's kwargs. - options: <type> - - header: list or dict - custom http header list or dict. - - cookie: str - cookie value. - - origin: str - custom origin url. - - suppress_origin: bool - suppress outputting origin header. - - host: <type> - custom host header string. - - http_proxy_host: <type> - http proxy host name. - - http_proxy_port: <type> - http proxy port. If not set, set to 80. - - http_no_proxy: <type> - host names, which doesn't use proxy. - - http_proxy_auth: <type> - http proxy auth information. tuple of username and password. default is None - - enable_multithread: bool - enable lock for multithread. - - redirect_limit: <type> - number of redirects to follow. - - sockopt: <type> - socket options - - sslopt: <type> - ssl option - - subprotocols: <type> - array of available sub protocols. default is None. - - skip_utf8_validation: bool - skip utf8 validation. - - socket: <type> - pre-initialized stream socket. + socket timeout time. This value could be either float/integer. + If set to None, it uses the default_timeout value. + http_proxy_host: str + HTTP proxy host name. + http_proxy_port: str or int + HTTP proxy port. If not set, set to 80. + http_no_proxy: list + Whitelisted host names that don't use the proxy. + http_proxy_auth: tuple + HTTP proxy auth information. tuple of username and password. Default is None. + enable_multithread: bool + Enable lock for multithread. + redirect_limit: int + Number of redirects to follow. + sockopt: tuple + Values for socket.setsockopt. + sockopt must be a tuple and each element is an argument of sock.setsockopt. + sslopt: dict + Optional dict object for ssl socket options. See FAQ for details. + subprotocols: list + List of available subprotocols. Default is None. + skip_utf8_validation: bool + Skip utf8 validation. + socket: socket + Pre-initialized stream socket. """ sockopt = options.pop("sockopt", []) sslopt = options.pop("sslopt", {}) fire_cont_frame = options.pop("fire_cont_frame", False) - enable_multithread = options.pop("enable_multithread", False) + enable_multithread = options.pop("enable_multithread", True) skip_utf8_validation = options.pop("skip_utf8_validation", False) websock = class_(sockopt=sockopt, sslopt=sslopt, fire_cont_frame=fire_cont_frame, diff --git a/libs/websocket/_exceptions.py b/libs/websocket/_exceptions.py index 83c6e42b7..b92b1f408 100644 --- a/libs/websocket/_exceptions.py +++ b/libs/websocket/_exceptions.py @@ -3,24 +3,22 @@ Define WebSocket exceptions """ """ +_exceptions.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ @@ -74,7 +72,7 @@ class WebSocketBadStatusException(WebSocketException): def __init__(self, message, status_code, status_message=None, resp_headers=None): msg = message % (status_code, status_message) - super(WebSocketBadStatusException, self).__init__(msg) + super().__init__(msg) self.status_code = status_code self.resp_headers = resp_headers diff --git a/libs/websocket/_handshake.py b/libs/websocket/_handshake.py index 74ccbaf03..f9dabb572 100644 --- a/libs/websocket/_handshake.py +++ b/libs/websocket/_handshake.py @@ -1,22 +1,20 @@ """ +_handshake.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import hashlib import hmac @@ -40,7 +38,7 @@ SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS CookieJar = SimpleCookieJar() -class handshake_response(object): +class handshake_response: def __init__(self, status, headers, subprotocol): self.status = status diff --git a/libs/websocket/_http.py b/libs/websocket/_http.py index a13667a24..603fa00fa 100644 --- a/libs/websocket/_http.py +++ b/libs/websocket/_http.py @@ -1,22 +1,20 @@ """ +_http.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import errno import os @@ -34,58 +32,72 @@ from base64 import encodebytes as base64encode __all__ = ["proxy_info", "connect", "read_headers"] try: - import socks - ProxyConnectionError = socks.ProxyConnectionError - HAS_PYSOCKS = True + from python_socks.sync import Proxy + from python_socks._errors import * + from python_socks._types import ProxyType + HAVE_PYTHON_SOCKS = True except: - class ProxyConnectionError(BaseException): + HAVE_PYTHON_SOCKS = False + + class ProxyError(Exception): + pass + + class ProxyTimeoutError(Exception): + pass + + class ProxyConnectionError(Exception): pass - HAS_PYSOCKS = False -class proxy_info(object): +class proxy_info: def __init__(self, **options): - self.type = options.get("proxy_type") or "http" - if not(self.type in ['http', 'socks4', 'socks5', 'socks5h']): - raise ValueError("proxy_type must be 'http', 'socks4', 'socks5' or 'socks5h'") - self.host = options.get("http_proxy_host", None) - if self.host: - self.port = options.get("http_proxy_port", 0) + self.proxy_host = options.get("http_proxy_host", None) + if self.proxy_host: + self.proxy_port = options.get("http_proxy_port", 0) self.auth = options.get("http_proxy_auth", None) self.no_proxy = options.get("http_no_proxy", None) + self.proxy_protocol = options.get("proxy_type", "http") + # Note: If timeout not specified, default python-socks timeout is 60 seconds + self.proxy_timeout = options.get("timeout", None) + if self.proxy_protocol not in ['http', 'socks4', 'socks4a', 'socks5', 'socks5h']: + raise ProxyError("Only http, socks4, socks5 proxy protocols are supported") else: - self.port = 0 + self.proxy_port = 0 self.auth = None self.no_proxy = None + self.proxy_protocol = "http" -def _open_proxied_socket(url, options, proxy): - hostname, port, resource, is_secure = parse_url(url) +def _start_proxied_socket(url, options, proxy): + if not HAVE_PYTHON_SOCKS: + raise WebSocketException("Python Socks is needed for SOCKS proxying but is not available") - if not HAS_PYSOCKS: - raise WebSocketException("PySocks module not found.") + hostname, port, resource, is_secure = parse_url(url) - ptype = socks.SOCKS5 - rdns = False - if proxy.type == "socks4": - ptype = socks.SOCKS4 - if proxy.type == "http": - ptype = socks.HTTP - if proxy.type[-1] == "h": + if proxy.proxy_protocol == "socks5": + rdns = False + proxy_type = ProxyType.SOCKS5 + if proxy.proxy_protocol == "socks4": + rdns = False + proxy_type = ProxyType.SOCKS4 + # socks5h and socks4a send DNS through proxy + if proxy.proxy_protocol == "socks5h": rdns = True + proxy_type = ProxyType.SOCKS5 + if proxy.proxy_protocol == "socks4a": + rdns = True + proxy_type = ProxyType.SOCKS4 - sock = socks.create_connection( - (hostname, port), - proxy_type=ptype, - proxy_addr=proxy.host, - proxy_port=proxy.port, - proxy_rdns=rdns, - proxy_username=proxy.auth[0] if proxy.auth else None, - proxy_password=proxy.auth[1] if proxy.auth else None, - timeout=options.timeout, - socket_options=DEFAULT_SOCKET_OPTION + options.sockopt - ) + ws_proxy = Proxy.create( + proxy_type=proxy_type, + host=proxy.proxy_host, + port=int(proxy.proxy_port), + username=proxy.auth[0] if proxy.auth else None, + password=proxy.auth[1] if proxy.auth else None, + rdns=rdns) + + sock = ws_proxy.connect(hostname, port, timeout=proxy.proxy_timeout) if is_secure and HAVE_SSL: sock = _ssl_socket(sock, options.sslopt, hostname) @@ -96,8 +108,11 @@ def _open_proxied_socket(url, options, proxy): def connect(url, options, proxy, socket): - if proxy.host and not socket and not (proxy.type == 'http'): - return _open_proxied_socket(url, options, proxy) + # Use _start_proxied_socket() only for socks4 or socks5 proxy + # Use _tunnel() for http proxy + # TODO: Use python-socks for http protocol also, to standardize flow + if proxy.proxy_host and not socket and not (proxy.proxy_protocol == "http"): + return _start_proxied_socket(url, options, proxy) hostname, port, resource, is_secure = parse_url(url) @@ -131,7 +146,7 @@ def connect(url, options, proxy, socket): def _get_addrinfo_list(hostname, port, is_secure, proxy): phost, pport, pauth = get_proxy_info( - hostname, is_secure, proxy.host, proxy.port, proxy.auth, proxy.no_proxy) + hostname, is_secure, proxy.proxy_host, proxy.proxy_port, proxy.auth, proxy.no_proxy) try: # when running on windows 10, getaddrinfo without socktype returns a socktype 0. # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0` @@ -168,10 +183,6 @@ def _open_socket(addrinfo_list, sockopt, timeout): while not err: try: sock.connect(address) - except ProxyConnectionError as error: - err = WebSocketProxyException(str(error)) - err.remote_ip = str(address[0]) - continue except socket.error as error: error.remote_ip = str(address[0]) try: @@ -200,33 +211,41 @@ def _open_socket(addrinfo_list, sockopt, timeout): def _wrap_sni_socket(sock, sslopt, hostname, check_hostname): - context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23)) - - if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: - cafile = sslopt.get('ca_certs', None) - capath = sslopt.get('ca_cert_path', None) - if cafile or capath: - context.load_verify_locations(cafile=cafile, capath=capath) - elif hasattr(context, 'load_default_certs'): - context.load_default_certs(ssl.Purpose.SERVER_AUTH) - if sslopt.get('certfile', None): - context.load_cert_chain( - sslopt['certfile'], - sslopt.get('keyfile', None), - sslopt.get('password', None), - ) - # see - # https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153 - context.verify_mode = sslopt['cert_reqs'] - if HAVE_CONTEXT_CHECK_HOSTNAME: - context.check_hostname = check_hostname - if 'ciphers' in sslopt: - context.set_ciphers(sslopt['ciphers']) - if 'cert_chain' in sslopt: - certfile, keyfile, password = sslopt['cert_chain'] - context.load_cert_chain(certfile, keyfile, password) - if 'ecdh_curve' in sslopt: - context.set_ecdh_curve(sslopt['ecdh_curve']) + context = sslopt.get('context', None) + if not context: + context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_TLS_CLIENT)) + + if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: + cafile = sslopt.get('ca_certs', None) + capath = sslopt.get('ca_cert_path', None) + if cafile or capath: + context.load_verify_locations(cafile=cafile, capath=capath) + elif hasattr(context, 'load_default_certs'): + context.load_default_certs(ssl.Purpose.SERVER_AUTH) + if sslopt.get('certfile', None): + context.load_cert_chain( + sslopt['certfile'], + sslopt.get('keyfile', None), + sslopt.get('password', None), + ) + + # Python 3.10 switch to PROTOCOL_TLS_CLIENT defaults to "cert_reqs = ssl.CERT_REQUIRED" and "check_hostname = True" + # If both disabled, set check_hostname before verify_mode + # see https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153 + if sslopt.get('cert_reqs', ssl.CERT_NONE) == ssl.CERT_NONE and not sslopt.get('check_hostname', False): + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + else: + context.check_hostname = sslopt.get('check_hostname', True) + context.verify_mode = sslopt.get('cert_reqs', ssl.CERT_REQUIRED) + + if 'ciphers' in sslopt: + context.set_ciphers(sslopt['ciphers']) + if 'cert_chain' in sslopt: + certfile, keyfile, password = sslopt['cert_chain'] + context.load_cert_chain(certfile, keyfile, password) + if 'ecdh_curve' in sslopt: + context.set_ecdh_curve(sslopt['ecdh_curve']) return context.wrap_socket( sock, @@ -248,12 +267,11 @@ def _ssl_socket(sock, user_sslopt, hostname): and user_sslopt.get('ca_cert_path', None) is None: sslopt['ca_cert_path'] = certPath - check_hostname = sslopt["cert_reqs"] != ssl.CERT_NONE and sslopt.pop( - 'check_hostname', True) - sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname) + if sslopt.get('server_hostname', None): + hostname = sslopt['server_hostname'] - if not HAVE_CONTEXT_CHECK_HOSTNAME and check_hostname: - match_hostname(sock.getpeercert(), hostname) + check_hostname = sslopt.get('check_hostname', True) + sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname) return sock diff --git a/libs/websocket/_logging.py b/libs/websocket/_logging.py index 07d900903..480d43b09 100644 --- a/libs/websocket/_logging.py +++ b/libs/websocket/_logging.py @@ -3,24 +3,22 @@ """ """ +_logging.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import logging diff --git a/libs/websocket/_socket.py b/libs/websocket/_socket.py index 70e163f27..4d9cc097e 100644 --- a/libs/websocket/_socket.py +++ b/libs/websocket/_socket.py @@ -3,24 +3,22 @@ """ """ +_socket.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import errno import selectors @@ -46,7 +44,7 @@ __all__ = ["DEFAULT_SOCKET_OPTION", "sock_opt", "setdefaulttimeout", "getdefault "recv", "recv_line", "send"] -class sock_opt(object): +class sock_opt: def __init__(self, sockopt, sslopt): if sockopt is None: @@ -125,7 +123,7 @@ def recv(sock, bufsize): if not bytes_: raise WebSocketConnectionClosedException( - "Connection is already closed.") + "Connection to remote host was lost.") return bytes_ diff --git a/libs/websocket/_ssl_compat.py b/libs/websocket/_ssl_compat.py index e0fc34917..f4af524e4 100644 --- a/libs/websocket/_ssl_compat.py +++ b/libs/websocket/_ssl_compat.py @@ -1,22 +1,20 @@ """ +_ssl_compat.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ __all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"] @@ -25,11 +23,6 @@ try: from ssl import SSLError from ssl import SSLWantReadError from ssl import SSLWantWriteError - HAVE_CONTEXT_CHECK_HOSTNAME = False - if hasattr(ssl, 'SSLContext') and hasattr(ssl.SSLContext, 'check_hostname'): - HAVE_CONTEXT_CHECK_HOSTNAME = True - - __all__.append("HAVE_CONTEXT_CHECK_HOSTNAME") HAVE_SSL = True except ImportError: # dummy class of SSLError for environment without ssl support diff --git a/libs/websocket/_url.py b/libs/websocket/_url.py index 32aaf23f6..f2a55019a 100644 --- a/libs/websocket/_url.py +++ b/libs/websocket/_url.py @@ -2,31 +2,29 @@ """ """ +_url.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import os import socket import struct -from urllib.parse import urlparse +from urllib.parse import unquote, urlparse __all__ = ["parse_url", "get_proxy_info"] @@ -109,7 +107,7 @@ def _is_address_in_network(ip, net): def _is_no_proxy_host(hostname, no_proxy): if not no_proxy: - v = os.environ.get("no_proxy", "").replace(" ", "") + v = os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")).replace(" ", "") if v: no_proxy = v.split(",") if not no_proxy: @@ -139,22 +137,22 @@ def get_proxy_info( Parameters ---------- - hostname: <type> - websocket server name. - is_secure: <type> - is the connection secure? (wss) looks for "https_proxy" in env + hostname: str + Websocket server name. + is_secure: bool + Is the connection secure? (wss) looks for "https_proxy" in env before falling back to "http_proxy" - options: <type> - - http_proxy_host: <type> - http proxy host name. - - http_proxy_port: <type> - http proxy port. - - http_no_proxy: <type> - host names, which doesn't use proxy. - - http_proxy_auth: <type> - http proxy auth information. tuple of username and password. default is None - - proxy_type: <type> - if set to "socks5" PySocks wrapper will be used in place of a http proxy. default is "http" + proxy_host: str + http proxy host name. + http_proxy_port: str or int + http proxy port. + http_no_proxy: list + Whitelisted host names that don't use the proxy. + http_proxy_auth: tuple + HTTP proxy auth information. Tuple of username and password. Default is None. + proxy_type: str + Specify the proxy protocol (http, socks4, socks4a, socks5, socks5h). Default is "http". + Use socks4a or socks5h if you want to send DNS requests through the proxy. """ if _is_no_proxy_host(hostname, no_proxy): return None, 0, None @@ -169,10 +167,10 @@ def get_proxy_info( env_keys.insert(0, "https_proxy") for key in env_keys: - value = os.environ.get(key, None) + value = os.environ.get(key, os.environ.get(key.upper(), "")).replace(" ", "") if value: proxy = urlparse(value) - auth = (proxy.username, proxy.password) if proxy.username else None + auth = (unquote(proxy.username), unquote(proxy.password)) if proxy.username else None return proxy.hostname, proxy.port, auth return None, 0, None diff --git a/libs/websocket/_utils.py b/libs/websocket/_utils.py index 0b9a93729..21fc437c5 100644 --- a/libs/websocket/_utils.py +++ b/libs/websocket/_utils.py @@ -1,27 +1,25 @@ """ +_url.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ __all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"] -class NoLock(object): +class NoLock: def __enter__(self): pass diff --git a/libs/websocket/_wsdump.py b/libs/websocket/_wsdump.py new file mode 100755 index 000000000..4d15f4134 --- /dev/null +++ b/libs/websocket/_wsdump.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 + +""" +wsdump.py +websocket - WebSocket client library for Python + +Copyright 2021 engn33r + +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. +""" + +import argparse +import code +import sys +import threading +import time +import ssl +import gzip +import zlib +from urllib.parse import urlparse + +import websocket + +try: + import readline +except ImportError: + pass + + +def get_encoding(): + encoding = getattr(sys.stdin, "encoding", "") + if not encoding: + return "utf-8" + else: + return encoding.lower() + + +OPCODE_DATA = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) +ENCODING = get_encoding() + + +class VAction(argparse.Action): + + def __call__(self, parser, args, values, option_string=None): + if values is None: + values = "1" + try: + values = int(values) + except ValueError: + values = values.count("v") + 1 + setattr(args, self.dest, values) + + +def parse_args(): + parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool") + parser.add_argument("url", metavar="ws_url", + help="websocket url. ex. ws://echo.websocket.org/") + parser.add_argument("-p", "--proxy", + help="proxy url. ex. http://127.0.0.1:8080") + parser.add_argument("-v", "--verbose", default=0, nargs='?', action=VAction, + dest="verbose", + help="set verbose mode. If set to 1, show opcode. " + "If set to 2, enable to trace websocket module") + parser.add_argument("-n", "--nocert", action='store_true', + help="Ignore invalid SSL cert") + parser.add_argument("-r", "--raw", action="store_true", + help="raw output") + parser.add_argument("-s", "--subprotocols", nargs='*', + help="Set subprotocols") + parser.add_argument("-o", "--origin", + help="Set origin") + parser.add_argument("--eof-wait", default=0, type=int, + help="wait time(second) after 'EOF' received.") + parser.add_argument("-t", "--text", + help="Send initial text") + parser.add_argument("--timings", action="store_true", + help="Print timings in seconds") + parser.add_argument("--headers", + help="Set custom headers. Use ',' as separator") + + return parser.parse_args() + + +class RawInput: + + def raw_input(self, prompt): + line = input(prompt) + + if ENCODING and ENCODING != "utf-8" and not isinstance(line, str): + line = line.decode(ENCODING).encode("utf-8") + elif isinstance(line, str): + line = line.encode("utf-8") + + return line + + +class InteractiveConsole(RawInput, code.InteractiveConsole): + + def write(self, data): + sys.stdout.write("\033[2K\033[E") + # sys.stdout.write("\n") + sys.stdout.write("\033[34m< " + data + "\033[39m") + sys.stdout.write("\n> ") + sys.stdout.flush() + + def read(self): + return self.raw_input("> ") + + +class NonInteractive(RawInput): + + def write(self, data): + sys.stdout.write(data) + sys.stdout.write("\n") + sys.stdout.flush() + + def read(self): + return self.raw_input("") + + +def main(): + start_time = time.time() + args = parse_args() + if args.verbose > 1: + websocket.enableTrace(True) + options = {} + if args.proxy: + p = urlparse(args.proxy) + options["http_proxy_host"] = p.hostname + options["http_proxy_port"] = p.port + if args.origin: + options["origin"] = args.origin + if args.subprotocols: + options["subprotocols"] = args.subprotocols + opts = {} + if args.nocert: + opts = {"cert_reqs": ssl.CERT_NONE, "check_hostname": False} + if args.headers: + options['header'] = list(map(str.strip, args.headers.split(','))) + ws = websocket.create_connection(args.url, sslopt=opts, **options) + if args.raw: + console = NonInteractive() + else: + console = InteractiveConsole() + print("Press Ctrl+C to quit") + + def recv(): + try: + frame = ws.recv_frame() + except websocket.WebSocketException: + return websocket.ABNF.OPCODE_CLOSE, None + if not frame: + raise websocket.WebSocketException("Not a valid frame %s" % frame) + elif frame.opcode in OPCODE_DATA: + return frame.opcode, frame.data + elif frame.opcode == websocket.ABNF.OPCODE_CLOSE: + ws.send_close() + return frame.opcode, None + elif frame.opcode == websocket.ABNF.OPCODE_PING: + ws.pong(frame.data) + return frame.opcode, frame.data + + return frame.opcode, frame.data + + def recv_ws(): + while True: + opcode, data = recv() + msg = None + if opcode == websocket.ABNF.OPCODE_TEXT and isinstance(data, bytes): + data = str(data, "utf-8") + if isinstance(data, bytes) and len(data) > 2 and data[:2] == b'\037\213': # gzip magick + try: + data = "[gzip] " + str(gzip.decompress(data), "utf-8") + except: + pass + elif isinstance(data, bytes): + try: + data = "[zlib] " + str(zlib.decompress(data, -zlib.MAX_WBITS), "utf-8") + except: + pass + + if isinstance(data, bytes): + data = repr(data) + + if args.verbose: + msg = "%s: %s" % (websocket.ABNF.OPCODE_MAP.get(opcode), data) + else: + msg = data + + if msg is not None: + if args.timings: + console.write(str(time.time() - start_time) + ": " + msg) + else: + console.write(msg) + + if opcode == websocket.ABNF.OPCODE_CLOSE: + break + + thread = threading.Thread(target=recv_ws) + thread.daemon = True + thread.start() + + if args.text: + ws.send(args.text) + + while True: + try: + message = console.read() + ws.send(message) + except KeyboardInterrupt: + return + except EOFError: + time.sleep(args.eof_wait) + return + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(e) diff --git a/libs/websocket/tests/echo-server.py b/libs/websocket/tests/echo-server.py new file mode 100644 index 000000000..08d108ab2 --- /dev/null +++ b/libs/websocket/tests/echo-server.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +# From https://github.com/aaugustin/websockets/blob/main/example/echo.py + +import asyncio +import websockets +import os + +LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '8765') + + +async def echo(websocket, path): + async for message in websocket: + await websocket.send(message) + + +async def main(): + async with websockets.serve(echo, "localhost", LOCAL_WS_SERVER_PORT): + await asyncio.Future() # run forever + +asyncio.run(main()) diff --git a/libs/websocket/tests/test_abnf.py b/libs/websocket/tests/test_abnf.py index d629c5c2a..7f156dc94 100644 --- a/libs/websocket/tests/test_abnf.py +++ b/libs/websocket/tests/test_abnf.py @@ -1,32 +1,27 @@ # -*- coding: utf-8 -*- # """ +test_abnf.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ -import os import websocket as ws from websocket._abnf import * -import sys import unittest -sys.path[0:0] = [""] class ABNFTest(unittest.TestCase): @@ -59,7 +54,7 @@ class ABNFTest(unittest.TestCase): def testMask(self): abnf_none_data = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING, mask=1, data=None) - bytes_val = bytes("aaaa", 'utf-8') + bytes_val = b"aaaa" self.assertEqual(abnf_none_data._get_masked(bytes_val), bytes_val) abnf_str_data = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING, mask=1, data="a") self.assertEqual(abnf_str_data._get_masked(bytes_val), b'aaaa\x00') diff --git a/libs/websocket/tests/test_app.py b/libs/websocket/tests/test_app.py index 2678793ab..cd1146b3a 100644 --- a/libs/websocket/tests/test_app.py +++ b/libs/websocket/tests/test_app.py @@ -1,42 +1,41 @@ # -*- coding: utf-8 -*- # """ +test_app.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import os import os.path import websocket as ws -import sys import ssl import unittest -sys.path[0:0] = [""] -# Skip test to access the internet. +# Skip test to access the internet unless TEST_WITH_INTERNET == 1 TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' +# Skip tests relying on local websockets server unless LOCAL_WS_SERVER_PORT != -1 +LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1') +TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1' TRACEABLE = True class WebSocketAppTest(unittest.TestCase): - class NotSetYet(object): + class NotSetYet: """ A marker class for signalling that a value hasn't been set yet. """ @@ -52,7 +51,7 @@ class WebSocketAppTest(unittest.TestCase): WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") def testKeepRunning(self): """ A WebSocketApp should keep running as long as its self.keep_running is not False (in the boolean context). @@ -62,16 +61,20 @@ class WebSocketAppTest(unittest.TestCase): """ Set the keep_running flag for later inspection and immediately close the connection. """ + self.send("hello!") WebSocketAppTest.keep_running_open = self.keep_running + self.keep_running = False + + def on_message(wsapp, message): + print(message) self.close() def on_close(self, *args, **kwargs): """ Set the keep_running flag for the test to use. """ WebSocketAppTest.keep_running_close = self.keep_running - self.send("connection should be closed here") - app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, on_close=on_close) + app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_open=on_open, on_close=on_close, on_message=on_message) app.run_forever() @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") diff --git a/libs/websocket/tests/test_cookiejar.py b/libs/websocket/tests/test_cookiejar.py index 8d2dff501..5bf1fcaeb 100644 --- a/libs/websocket/tests/test_cookiejar.py +++ b/libs/websocket/tests/test_cookiejar.py @@ -3,27 +3,24 @@ """ """ +test_cookiejar.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import unittest - from websocket._cookiejar import SimpleCookieJar @@ -116,3 +113,7 @@ class CookieJarTest(unittest.TestCase): self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d") self.assertEqual(cookie_jar.get("abc.com.es"), "") self.assertEqual(cookie_jar.get("xabc.com"), "") + + +if __name__ == "__main__": + unittest.main() diff --git a/libs/websocket/tests/test_http.py b/libs/websocket/tests/test_http.py index 2a059eac1..fda467d7b 100644 --- a/libs/websocket/tests/test_http.py +++ b/libs/websocket/tests/test_http.py @@ -1,43 +1,47 @@ # -*- coding: utf-8 -*- # """ +test_http.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ import os import os.path import websocket as ws -from websocket._http import proxy_info, read_headers, _open_proxied_socket, _tunnel, _get_addrinfo_list, connect -import sys +from websocket._http import proxy_info, read_headers, _start_proxied_socket, _tunnel, _get_addrinfo_list, connect import unittest import ssl import websocket -import socks import socket -sys.path[0:0] = [""] -# Skip test to access the internet. +try: + from python_socks._errors import ProxyError, ProxyTimeoutError, ProxyConnectionError +except: + from websocket._http import ProxyError, ProxyTimeoutError, ProxyConnectionError + +# Skip test to access the internet unless TEST_WITH_INTERNET == 1 TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' +TEST_WITH_PROXY = os.environ.get('TEST_WITH_PROXY', '0') == '1' +# Skip tests relying on local websockets server unless LOCAL_WS_SERVER_PORT != -1 +LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1') +TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1' -class SockMock(object): +class SockMock: def __init__(self): self.data = [] self.sent = [] @@ -79,6 +83,7 @@ class OptsList(): def __init__(self): self.timeout = 1 self.sockopt = [] + self.sslopt = {"cert_reqs": ssl.CERT_NONE} class HttpTest(unittest.TestCase): @@ -96,15 +101,19 @@ class HttpTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testConnect(self): - # Not currently testing an actual proxy connection, so just check whether TypeError is raised. This requires internet for a DNS lookup - self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host=None, http_proxy_port=None, proxy_type=None)) - self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http")) - self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4")) - self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5h")) - self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http")) - self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http")) - self.assertRaises(socks.ProxyConnectionError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=8080, proxy_type="socks4"), None) - self.assertRaises(socket.timeout, connect, "wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=8080, proxy_type="http"), None) + # Not currently testing an actual proxy connection, so just check whether proxy errors are raised. This requires internet for a DNS lookup + if ws._http.HAVE_PYTHON_SOCKS: + # Need this check, otherwise case where python_socks is not installed triggers + # websocket._exceptions.WebSocketException: Python Socks is needed for SOCKS proxying but is not available + self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4", timeout=1)) + self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4a", timeout=1)) + self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5", timeout=1)) + self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5h", timeout=1)) + self.assertRaises(ProxyConnectionError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=9999, proxy_type="socks4", timeout=1), None) + + self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http")) + self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http")) + self.assertRaises(socket.timeout, connect, "wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=9999, proxy_type="http", timeout=1), None) self.assertEqual( connect("wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=8080, proxy_type="http"), True), (True, ("google.com", 443, "/"))) @@ -112,11 +121,26 @@ class HttpTest(unittest.TestCase): # self.assertRaises(OverflowError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=99999, proxy_type="socks4", timeout=2), False) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + @unittest.skipUnless(TEST_WITH_PROXY, "This test requires a HTTP proxy to be running on port 8899") + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + def testProxyConnect(self): + ws = websocket.WebSocket() + ws.connect("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http") + ws.send("Hello, Server") + server_response = ws.recv() + self.assertEqual(server_response, "Hello, Server") + # self.assertEqual(_start_proxied_socket("wss://api.bitfinex.com/ws/2", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http"))[1], ("api.bitfinex.com", 443, '/ws/2')) + self.assertEqual(_get_addrinfo_list("api.bitfinex.com", 443, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http")), + (socket.getaddrinfo("127.0.0.1", 8899, 0, socket.SOCK_STREAM, socket.SOL_TCP), True, None)) + self.assertEqual(connect("wss://api.bitfinex.com/ws/2", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=8899, proxy_type="http"), None)[1], ("api.bitfinex.com", 443, '/ws/2')) + # TODO: Test SOCKS4 and SOCK5 proxies with unit tests + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testSSLopt(self): ssloptions = { - "cert_reqs": ssl.CERT_NONE, "check_hostname": False, - "ssl_version": ssl.PROTOCOL_SSLv23, + "server_hostname": "ServerName", + "ssl_version": ssl.PROTOCOL_TLS_CLIENT, "ciphers": "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:\ TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\ ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:\ @@ -139,11 +163,13 @@ class HttpTest(unittest.TestCase): ws_ssl2.close def testProxyInfo(self): - self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").type, "http") - self.assertRaises(ValueError, proxy_info, http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="badval") - self.assertEqual(proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http").host, "example.com") - self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").port, "8080") + self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").proxy_protocol, "http") + self.assertRaises(ProxyError, proxy_info, http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="badval") + self.assertEqual(proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http").proxy_host, "example.com") + self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").proxy_port, "8080") self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").auth, None) + self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http", http_proxy_auth=("my_username123", "my_pass321")).auth[0], "my_username123") + self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http", http_proxy_auth=("my_username123", "my_pass321")).auth[1], "my_pass321") if __name__ == "__main__": diff --git a/libs/websocket/tests/test_url.py b/libs/websocket/tests/test_url.py index a33e93437..ad3a3b1b3 100644 --- a/libs/websocket/tests/test_url.py +++ b/libs/websocket/tests/test_url.py @@ -1,30 +1,26 @@ # -*- coding: utf-8 -*- # """ +test_url.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ -import sys import os import unittest -sys.path[0:0] = [""] from websocket._url import get_proxy_info, parse_url, _is_address_in_network, _is_no_proxy_host @@ -277,6 +273,10 @@ class ProxyInfoTest(unittest.TestCase): os.environ["https_proxy"] = "http://a:b@localhost2:3128/" self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("a", "b"))) + os.environ["http_proxy"] = "http://john%40example.com:P%40SSWORD@localhost:3128/" + os.environ["https_proxy"] = "http://john%40example.com:P%40SSWORD@localhost2:3128/" + self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("[email protected]", "P@SSWORD"))) + os.environ["http_proxy"] = "http://a:b@localhost/" os.environ["https_proxy"] = "http://a:b@localhost2/" os.environ["no_proxy"] = "example1.com,example2.com" diff --git a/libs/websocket/tests/test_websocket.py b/libs/websocket/tests/test_websocket.py index 35a928cdf..8b34aa51f 100644 --- a/libs/websocket/tests/test_websocket.py +++ b/libs/websocket/tests/test_websocket.py @@ -5,28 +5,24 @@ """ """ +test_websocket.py websocket - WebSocket client library for Python -Copyright (C) 2010 Hiroki Ohtani(liris) +Copyright 2021 engn33r - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +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 - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + 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. """ -import sys -sys.path[0:0] = [""] import os import os.path import socket @@ -47,8 +43,11 @@ except ImportError: class SSLError(Exception): pass -# Skip test to access the internet. +# Skip test to access the internet unless TEST_WITH_INTERNET == 1 TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' +# Skip tests relying on local websockets server unless LOCAL_WS_SERVER_PORT != -1 +LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1') +TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1' TRACEABLE = True @@ -56,7 +55,7 @@ def create_mask_key(_): return "abcd" -class SockMock(object): +class SockMock: def __init__(self): self.data = [] self.sent = [] @@ -287,7 +286,7 @@ class WebSocketTest(unittest.TestCase): def testClose(self): sock = ws.WebSocket() sock.connected = True - self.assertRaises(ws._exceptions.WebSocketConnectionClosedException, sock.close) + sock.close sock = ws.WebSocket() s = sock.sock = SockMock() @@ -337,12 +336,13 @@ class WebSocketTest(unittest.TestCase): s.sent[0], b'\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17') - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") def testWebSocket(self): - s = ws.create_connection("ws://echo.websocket.org/") + s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT) self.assertNotEqual(s, None) s.send("Hello, World") - result = s.recv() + result = s.next() + s.fileno() self.assertEqual(result, "Hello, World") s.send("こにゃにゃちは、世界") @@ -351,15 +351,21 @@ class WebSocketTest(unittest.TestCase): self.assertRaises(ValueError, s.send_close, -1, "") s.close() - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") def testPingPong(self): - s = ws.create_connection("ws://echo.websocket.org/") + s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT) self.assertNotEqual(s, None) s.ping("Hello") s.pong("Hi") s.close() @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testSupportRedirect(self): + s = ws.WebSocket() + self.assertRaises(ws._exceptions.WebSocketBadStatusException, s.connect, "ws://google.com/") + # Need to find a URL that has a redirect code leading to a websocket + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testSecureWebSocket(self): import ssl s = ws.create_connection("wss://api.bitfinex.com/ws/2") @@ -372,9 +378,9 @@ class WebSocketTest(unittest.TestCase): self.assertEqual(s.getsubprotocol(), None) s.abort() - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") def testWebSocketWithCustomHeader(self): - s = ws.create_connection("ws://echo.websocket.org/", + s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, headers={"User-Agent": "PythonWebsocketClient"}) self.assertNotEqual(s, None) s.send("Hello, World") @@ -383,9 +389,9 @@ class WebSocketTest(unittest.TestCase): self.assertRaises(ValueError, s.close, -1, "") s.close() - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") def testAfterClose(self): - s = ws.create_connection("ws://echo.websocket.org/") + s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT) self.assertNotEqual(s, None) s.close() self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello") @@ -393,10 +399,10 @@ class WebSocketTest(unittest.TestCase): class SockOptTest(unittest.TestCase): - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") def testSockOpt(self): sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),) - s = ws.create_connection("ws://echo.websocket.org", sockopt=sockopt) + s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, sockopt=sockopt) self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0) s.close() @@ -414,7 +420,7 @@ class UtilsTest(unittest.TestCase): class HandshakeTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def test_http_SSL(self): - websock1 = ws.WebSocket(sslopt={"cert_chain": ssl.get_default_verify_paths().capath}) + websock1 = ws.WebSocket(sslopt={"cert_chain": ssl.get_default_verify_paths().capath}, enable_multithread=False) self.assertRaises(ValueError, websock1.connect, "wss://api.bitfinex.com/ws/2") websock2 = ws.WebSocket(sslopt={"certfile": "myNonexistentCertFile"}) @@ -423,9 +429,8 @@ class HandshakeTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testManualHeaders(self): - websock3 = ws.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE, - "ca_certs": ssl.get_default_verify_paths().capath, - "ca_cert_path": ssl.get_default_verify_paths().openssl_cafile}) + websock3 = ws.WebSocket(sslopt={"ca_certs": ssl.get_default_verify_paths().cafile, + "ca_cert_path": ssl.get_default_verify_paths().capath}) self.assertRaises(ws._exceptions.WebSocketBadStatusException, websock3.connect, "wss://api.bitfinex.com/ws/2", cookie="chocolate", origin="testing_websockets.com", |