mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 05:01:14 -07:00
Update websocket-client-1.2.1
This commit is contained in:
parent
c67f18d65c
commit
aa127ecbda
21 changed files with 1756 additions and 1045 deletions
|
@ -1,109 +1,118 @@
|
|||
"""
|
||||
_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-1335 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
|
||||
import socket
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from ._exceptions import *
|
||||
from ._logging import *
|
||||
from ._socket import*
|
||||
from ._ssl_compat import *
|
||||
from ._url import *
|
||||
|
||||
if six.PY3:
|
||||
from base64 import encodebytes as base64encode
|
||||
else:
|
||||
from base64 import encodestring as base64encode
|
||||
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
|
||||
HAS_PYSOCKS = False
|
||||
|
||||
class ProxyTimeoutError(Exception):
|
||||
pass
|
||||
|
||||
class ProxyConnectionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class proxy_info(object):
|
||||
|
||||
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):
|
||||
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")
|
||||
|
||||
hostname, port, resource, is_secure = parse_url(url)
|
||||
|
||||
if not HAS_PYSOCKS:
|
||||
raise WebSocketException("PySocks module not found.")
|
||||
|
||||
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)
|
||||
|
||||
if is_secure:
|
||||
if HAVE_SSL:
|
||||
sock = _ssl_socket(sock, options.sslopt, hostname)
|
||||
else:
|
||||
raise WebSocketException("SSL not available.")
|
||||
sock = ws_proxy.connect(hostname, port, timeout=proxy.proxy_timeout)
|
||||
|
||||
if is_secure and HAVE_SSL:
|
||||
sock = _ssl_socket(sock, options.sslopt, hostname)
|
||||
elif is_secure:
|
||||
raise WebSocketException("SSL not available.")
|
||||
|
||||
return sock, (hostname, port, resource)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
@ -137,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`
|
||||
|
@ -174,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:
|
||||
|
@ -190,6 +195,8 @@ def _open_socket(addrinfo_list, sockopt, timeout):
|
|||
err = error
|
||||
continue
|
||||
else:
|
||||
if sock:
|
||||
sock.close()
|
||||
raise error
|
||||
else:
|
||||
break
|
||||
|
@ -203,12 +210,8 @@ def _open_socket(addrinfo_list, sockopt, timeout):
|
|||
return sock
|
||||
|
||||
|
||||
def _can_use_sni():
|
||||
return six.PY2 and sys.version_info >= (2, 7, 9) or sys.version_info >= (3, 2)
|
||||
|
||||
|
||||
def _wrap_sni_socket(sock, sslopt, hostname, check_hostname):
|
||||
context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23))
|
||||
context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_TLS))
|
||||
|
||||
if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE:
|
||||
cafile = sslopt.get('ca_certs', None)
|
||||
|
@ -250,21 +253,18 @@ def _ssl_socket(sock, user_sslopt, hostname):
|
|||
|
||||
certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE')
|
||||
if certPath and os.path.isfile(certPath) \
|
||||
and user_sslopt.get('ca_certs', None) is None \
|
||||
and user_sslopt.get('ca_cert', None) is None:
|
||||
and user_sslopt.get('ca_certs', None) is None:
|
||||
sslopt['ca_certs'] = certPath
|
||||
elif certPath and os.path.isdir(certPath) \
|
||||
and user_sslopt.get('ca_cert_path', None) is None:
|
||||
sslopt['ca_cert_path'] = certPath
|
||||
|
||||
if sslopt.get('server_hostname', None):
|
||||
hostname = sslopt['server_hostname']
|
||||
|
||||
check_hostname = sslopt["cert_reqs"] != ssl.CERT_NONE and sslopt.pop(
|
||||
'check_hostname', True)
|
||||
|
||||
if _can_use_sni():
|
||||
sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname)
|
||||
else:
|
||||
sslopt.pop('check_hostname', True)
|
||||
sock = ssl.wrap_socket(sock, **sslopt)
|
||||
sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname)
|
||||
|
||||
if not HAVE_CONTEXT_CHECK_HOSTNAME and check_hostname:
|
||||
match_hostname(sock.getpeercert(), hostname)
|
||||
|
@ -274,7 +274,9 @@ def _ssl_socket(sock, user_sslopt, hostname):
|
|||
|
||||
def _tunnel(sock, host, port, auth):
|
||||
debug("Connecting proxy...")
|
||||
connect_header = "CONNECT %s:%d HTTP/1.0\r\n" % (host, port)
|
||||
connect_header = "CONNECT %s:%d HTTP/1.1\r\n" % (host, port)
|
||||
connect_header += "Host: %s:%d\r\n" % (host, port)
|
||||
|
||||
# TODO: support digest auth.
|
||||
if auth and auth[0]:
|
||||
auth_str = auth[0]
|
||||
|
@ -321,7 +323,10 @@ def read_headers(sock):
|
|||
kv = line.split(":", 1)
|
||||
if len(kv) == 2:
|
||||
key, value = kv
|
||||
headers[key.lower()] = value.strip()
|
||||
if key.lower() == "set-cookie" and headers.get("set-cookie"):
|
||||
headers["set-cookie"] = headers.get("set-cookie") + "; " + value.strip()
|
||||
else:
|
||||
headers[key.lower()] = value.strip()
|
||||
else:
|
||||
raise WebSocketException("Invalid header")
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue