|
|
|
|
|
import os |
|
import os.path |
|
import socket |
|
import ssl |
|
import unittest |
|
|
|
import websocket |
|
from websocket._exceptions import WebSocketProxyException, WebSocketException |
|
from websocket._http import ( |
|
_get_addrinfo_list, |
|
_start_proxied_socket, |
|
_tunnel, |
|
connect, |
|
proxy_info, |
|
read_headers, |
|
HAVE_PYTHON_SOCKS, |
|
) |
|
|
|
""" |
|
test_http.py |
|
websocket - WebSocket client library for Python |
|
|
|
Copyright 2024 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. |
|
""" |
|
|
|
try: |
|
from python_socks._errors import ProxyConnectionError, ProxyError, ProxyTimeoutError |
|
except: |
|
from websocket._http import ProxyConnectionError, ProxyError, ProxyTimeoutError |
|
|
|
|
|
TEST_WITH_INTERNET = os.environ.get("TEST_WITH_INTERNET", "0") == "1" |
|
TEST_WITH_PROXY = os.environ.get("TEST_WITH_PROXY", "0") == "1" |
|
|
|
LOCAL_WS_SERVER_PORT = os.environ.get("LOCAL_WS_SERVER_PORT", "-1") |
|
TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != "-1" |
|
|
|
|
|
class SockMock: |
|
def __init__(self): |
|
self.data = [] |
|
self.sent = [] |
|
|
|
def add_packet(self, data): |
|
self.data.append(data) |
|
|
|
def gettimeout(self): |
|
return None |
|
|
|
def recv(self, bufsize): |
|
if self.data: |
|
e = self.data.pop(0) |
|
if isinstance(e, Exception): |
|
raise e |
|
if len(e) > bufsize: |
|
self.data.insert(0, e[bufsize:]) |
|
return e[:bufsize] |
|
|
|
def send(self, data): |
|
self.sent.append(data) |
|
return len(data) |
|
|
|
def close(self): |
|
pass |
|
|
|
|
|
class HeaderSockMock(SockMock): |
|
def __init__(self, fname): |
|
SockMock.__init__(self) |
|
path = os.path.join(os.path.dirname(__file__), fname) |
|
with open(path, "rb") as f: |
|
self.add_packet(f.read()) |
|
|
|
|
|
class OptsList: |
|
def __init__(self): |
|
self.timeout = 1 |
|
self.sockopt = [] |
|
self.sslopt = {"cert_reqs": ssl.CERT_NONE} |
|
|
|
|
|
class HttpTest(unittest.TestCase): |
|
def test_read_header(self): |
|
status, header, _ = read_headers(HeaderSockMock("data/header01.txt")) |
|
self.assertEqual(status, 101) |
|
self.assertEqual(header["connection"], "Upgrade") |
|
|
|
self.assertRaises( |
|
WebSocketException, read_headers, HeaderSockMock("data/header02.txt") |
|
) |
|
|
|
def test_tunnel(self): |
|
self.assertRaises( |
|
WebSocketProxyException, |
|
_tunnel, |
|
HeaderSockMock("data/header01.txt"), |
|
"example.com", |
|
80, |
|
("username", "password"), |
|
) |
|
self.assertRaises( |
|
WebSocketProxyException, |
|
_tunnel, |
|
HeaderSockMock("data/header02.txt"), |
|
"example.com", |
|
80, |
|
("username", "password"), |
|
) |
|
|
|
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") |
|
def test_connect(self): |
|
|
|
if HAVE_PYTHON_SOCKS: |
|
|
|
|
|
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", |
|
http_proxy_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", |
|
http_proxy_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", |
|
http_proxy_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", |
|
http_proxy_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", |
|
http_proxy_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", |
|
http_proxy_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, "/")), |
|
) |
|
|
|
|
|
|
|
@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 test_proxy_connect(self): |
|
ws = websocket.WebSocket() |
|
ws.connect( |
|
f"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( |
|
_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"), |
|
) |
|
|
|
|
|
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") |
|
def test_sslopt(self): |
|
ssloptions = { |
|
"check_hostname": False, |
|
"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:\ |
|
ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\ |
|
DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:\ |
|
ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:\ |
|
ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:\ |
|
DHE-RSA-AES256-SHA256:ECDHE-ECDSA-AES128-SHA256:\ |
|
ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:\ |
|
ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA", |
|
"ecdh_curve": "prime256v1", |
|
} |
|
ws_ssl1 = websocket.WebSocket(sslopt=ssloptions) |
|
ws_ssl1.connect("wss://api.bitfinex.com/ws/2") |
|
ws_ssl1.send("Hello") |
|
ws_ssl1.close() |
|
|
|
ws_ssl2 = websocket.WebSocket(sslopt={"check_hostname": True}) |
|
ws_ssl2.connect("wss://api.bitfinex.com/ws/2") |
|
ws_ssl2.close |
|
|
|
def test_proxy_info(self): |
|
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__": |
|
unittest.main() |
|
|