Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
_http.py412 linesDownload Raw Back to websocket
1"""2_http.py3websocket - WebSocket client library for Python4 5Copyright 2025 engn33r6 7Licensed under the Apache License, Version 2.0 (the "License");8you may not use this file except in compliance with the License.9You may obtain a copy of the License at10 11    http://www.apache.org/licenses/LICENSE-2.012 13Unless required by applicable law or agreed to in writing, software14distributed under the License is distributed on an "AS IS" BASIS,15WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.16See the License for the specific language governing permissions and17limitations under the License.18"""19 20import errno21import os22import socket23from base64 import encodebytes as base64encode24 25from ._exceptions import (26    WebSocketAddressException,27    WebSocketException,28    WebSocketProxyException,29)30from ._logging import debug, dump, trace31from ._socket import DEFAULT_SOCKET_OPTION, recv_line, send32from ._ssl_compat import HAVE_SSL, ssl33from ._url import get_proxy_info, parse_url34 35__all__ = ["proxy_info", "connect", "read_headers"]36 37try:38    from python_socks._errors import ProxyConnectionError, ProxyError, ProxyTimeoutError39    from python_socks._types import ProxyType40    from python_socks.sync import Proxy41 42    HAVE_PYTHON_SOCKS = True43except:44    HAVE_PYTHON_SOCKS = False45 46    class ProxyError(Exception):47        pass48 49    class ProxyTimeoutError(Exception):50        pass51 52    class ProxyConnectionError(Exception):53        pass54 55 56class proxy_info:57    def __init__(self, **options):58        self.proxy_host = options.get("http_proxy_host", None)59        if self.proxy_host:60            self.proxy_port = options.get("http_proxy_port", 0)61            self.auth = options.get("http_proxy_auth", None)62            self.no_proxy = options.get("http_no_proxy", None)63            self.proxy_protocol = options.get("proxy_type", "http")64            # Note: If timeout not specified, default python-socks timeout is 60 seconds65            self.proxy_timeout = options.get("http_proxy_timeout", None)66            if self.proxy_protocol not in [67                "http",68                "socks4",69                "socks4a",70                "socks5",71                "socks5h",72            ]:73                raise ProxyError(74                    "Only http, socks4, socks5 proxy protocols are supported"75                )76        else:77            self.proxy_port = 078            self.auth = None79            self.no_proxy = None80            self.proxy_protocol = "http"81 82 83def _start_proxied_socket(url: str, options, proxy) -> tuple:84    if not HAVE_PYTHON_SOCKS:85        raise WebSocketException(86            "Python Socks is needed for SOCKS proxying but is not available"87        )88 89    hostname, port, resource, is_secure = parse_url(url)90 91    if proxy.proxy_protocol == "socks4":92        rdns = False93        proxy_type = ProxyType.SOCKS494    # socks4a sends DNS through proxy95    elif proxy.proxy_protocol == "socks4a":96        rdns = True97        proxy_type = ProxyType.SOCKS498    elif proxy.proxy_protocol == "socks5":99        rdns = False100        proxy_type = ProxyType.SOCKS5101    # socks5h sends DNS through proxy102    elif proxy.proxy_protocol == "socks5h":103        rdns = True104        proxy_type = ProxyType.SOCKS5105 106    ws_proxy = Proxy.create(107        proxy_type=proxy_type,108        host=proxy.proxy_host,109        port=int(proxy.proxy_port),110        username=proxy.auth[0] if proxy.auth else None,111        password=proxy.auth[1] if proxy.auth else None,112        rdns=rdns,113    )114 115    sock = ws_proxy.connect(hostname, port, timeout=proxy.proxy_timeout)116 117    if is_secure:118        if HAVE_SSL:119            sock = _ssl_socket(sock, options.sslopt, hostname)120        else:121            raise WebSocketException("SSL not available.")122 123    return sock, (hostname, port, resource)124 125 126def connect(url: str, options, proxy, socket):127    # Use _start_proxied_socket() only for socks4 or socks5 proxy128    # Use _tunnel() for http proxy129    # TODO: Use python-socks for http protocol also, to standardize flow130    if proxy.proxy_host and not socket and proxy.proxy_protocol != "http":131        return _start_proxied_socket(url, options, proxy)132 133    hostname, port_from_url, resource, is_secure = parse_url(url)134 135    if socket:136        return socket, (hostname, port_from_url, resource)137 138    addrinfo_list, need_tunnel, auth = _get_addrinfo_list(139        hostname, port_from_url, is_secure, proxy140    )141    if not addrinfo_list:142        raise WebSocketException(f"Host not found.: {hostname}:{port_from_url}")143 144    sock = None145    try:146        sock = _open_socket(addrinfo_list, options.sockopt, options.timeout)147        if need_tunnel:148            sock = _tunnel(sock, hostname, port_from_url, auth)149 150        if is_secure:151            if HAVE_SSL:152                sock = _ssl_socket(sock, options.sslopt, hostname)153            else:154                raise WebSocketException("SSL not available.")155 156        return sock, (hostname, port_from_url, resource)157    except:158        if sock:159            sock.close()160        raise161 162 163def _get_addrinfo_list(hostname, port: int, is_secure: bool, proxy) -> tuple:164    phost, pport, pauth = get_proxy_info(165        hostname,166        is_secure,167        proxy.proxy_host,168        proxy.proxy_port,169        proxy.auth,170        proxy.no_proxy,171    )172    try:173        # when running on windows 10, getaddrinfo without socktype returns a socktype 0.174        # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0`175        # or `OSError: [Errno 22] Invalid argument` when creating socket. Force the socket type to SOCK_STREAM.176        if not phost:177            addrinfo_list = socket.getaddrinfo(178                hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP179            )180            return addrinfo_list, False, None181        else:182            pport = pport and pport or 80183            # when running on windows 10, the getaddrinfo used above184            # returns a socktype 0. This generates an error exception:185            # _on_error: exception Socket type must be stream or datagram, not 0186            # Force the socket type to SOCK_STREAM187            addrinfo_list = socket.getaddrinfo(188                phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP189            )190            return addrinfo_list, True, pauth191    except socket.gaierror as e:192        raise WebSocketAddressException(e)193 194 195def _open_socket(addrinfo_list, sockopt, timeout):196    err = None197    for addrinfo in addrinfo_list:198        family, socktype, proto = addrinfo[:3]199        sock = socket.socket(family, socktype, proto)200        sock.settimeout(timeout)201        for opts in DEFAULT_SOCKET_OPTION:202            sock.setsockopt(*opts)203        for opts in sockopt:204            sock.setsockopt(*opts)205 206        address = addrinfo[4]207        err = None208        while not err:209            try:210                sock.connect(address)211            except socket.error as error:212                sock.close()213                error.remote_ip = str(address[0])214                try:215                    eConnRefused = (216                        errno.ECONNREFUSED,217                        errno.WSAECONNREFUSED,218                        errno.ENETUNREACH,219                    )220                except AttributeError:221                    eConnRefused = (errno.ECONNREFUSED, errno.ENETUNREACH)222                if error.errno not in eConnRefused:223                    raise error224                err = error225                continue226            else:227                break228        else:229            continue230        break231    else:232        if err:233            raise err234 235    return sock236 237 238def _wrap_sni_socket(sock: socket.socket, sslopt: dict, hostname, check_hostname):239    context = sslopt.get("context", None)240    if not context:241        context = ssl.SSLContext(sslopt.get("ssl_version", ssl.PROTOCOL_TLS_CLIENT))242        # Non default context need to manually enable SSLKEYLOGFILE support by setting the keylog_filename attribute.243        # For more details see also:244        # * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#context-creation245        # * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#ssl.SSLContext.keylog_filename246        keylog_file = os.environ.get("SSLKEYLOGFILE")247        if keylog_file is not None:248            context.keylog_filename = keylog_file249 250        if sslopt.get("cert_reqs", ssl.CERT_NONE) != ssl.CERT_NONE:251            cafile = sslopt.get("ca_certs", None)252            capath = sslopt.get("ca_cert_path", None)253            if cafile or capath:254                try:255                    context.load_verify_locations(cafile=cafile, capath=capath)256                except (FileNotFoundError, ssl.SSLError, ValueError) as e:257                    raise WebSocketException(f"SSL CA certificate loading failed: {e}")258            elif hasattr(context, "load_default_certs"):259                try:260                    context.load_default_certs(ssl.Purpose.SERVER_AUTH)261                except ssl.SSLError as e:262                    raise WebSocketException(263                        f"SSL default certificate loading failed: {e}"264                    )265        if sslopt.get("certfile", None):266            try:267                context.load_cert_chain(268                    sslopt["certfile"],269                    sslopt.get("keyfile", None),270                    sslopt.get("password", None),271                )272            except (FileNotFoundError, ValueError) as e:273                raise WebSocketException(f"SSL client certificate loading failed: {e}")274            except ssl.SSLError as e:275                raise WebSocketException(f"SSL client certificate loading failed: {e}")276 277        # Python 3.10 switch to PROTOCOL_TLS_CLIENT defaults to "cert_reqs = ssl.CERT_REQUIRED" and "check_hostname = True"278        # If both disabled, set check_hostname before verify_mode279        # see https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153280        if sslopt.get("cert_reqs", ssl.CERT_NONE) == ssl.CERT_NONE and not sslopt.get(281            "check_hostname", False282        ):283            context.check_hostname = False284            context.verify_mode = ssl.CERT_NONE285        else:286            context.check_hostname = sslopt.get("check_hostname", True)287            context.verify_mode = sslopt.get("cert_reqs", ssl.CERT_REQUIRED)288 289        if "ciphers" in sslopt:290            try:291                context.set_ciphers(sslopt["ciphers"])292            except ssl.SSLError as e:293                raise WebSocketException(f"SSL cipher configuration failed: {e}")294        if "cert_chain" in sslopt:295            try:296                cert_chain = sslopt["cert_chain"]297                if not isinstance(cert_chain, (tuple, list)) or len(cert_chain) != 3:298                    raise ValueError(299                        "cert_chain must be a tuple/list of (certfile, keyfile, password)"300                    )301                certfile, keyfile, password = cert_chain302                context.load_cert_chain(certfile, keyfile, password)303            except ValueError:304                raise305            except (FileNotFoundError, ssl.SSLError) as e:306                raise WebSocketException(307                    f"SSL client certificate configuration failed: {e}"308                )309        if "ecdh_curve" in sslopt:310            try:311                context.set_ecdh_curve(sslopt["ecdh_curve"])312            except ValueError as e:313                raise WebSocketException(f"SSL ECDH curve configuration failed: {e}")314 315    return context.wrap_socket(316        sock,317        do_handshake_on_connect=sslopt.get("do_handshake_on_connect", True),318        suppress_ragged_eofs=sslopt.get("suppress_ragged_eofs", True),319        server_hostname=hostname,320    )321 322 323def _ssl_socket(sock: socket.socket, user_sslopt: dict, hostname):324    sslopt: dict = {"cert_reqs": ssl.CERT_REQUIRED}325    sslopt.update(user_sslopt)326 327    cert_path = os.environ.get("WEBSOCKET_CLIENT_CA_BUNDLE")328    if (329        cert_path330        and os.path.isfile(cert_path)331        and user_sslopt.get("ca_certs", None) is None332    ):333        sslopt["ca_certs"] = cert_path334    elif (335        cert_path336        and os.path.isdir(cert_path)337        and user_sslopt.get("ca_cert_path", None) is None338    ):339        sslopt["ca_cert_path"] = cert_path340 341    if sslopt.get("server_hostname", None):342        hostname = sslopt["server_hostname"]343 344    check_hostname = sslopt.get("check_hostname", True)345    sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname)346 347    return sock348 349 350def _tunnel(sock: socket.socket, host, port: int, auth) -> socket.socket:351    debug("Connecting proxy...")352    connect_header = f"CONNECT {host}:{port} HTTP/1.1\r\n"353    connect_header += f"Host: {host}:{port}\r\n"354 355    # TODO: support digest auth.356    if auth and auth[0]:357        auth_str = auth[0]358        if auth[1]:359            auth_str += f":{auth[1]}"360        encoded_str = base64encode(auth_str.encode()).strip().decode().replace("\n", "")361        connect_header += f"Proxy-Authorization: Basic {encoded_str}\r\n"362    connect_header += "\r\n"363    dump("request header", connect_header)364 365    send(sock, connect_header)366 367    try:368        status, _, _ = read_headers(sock)369    except (socket.error, WebSocketException) as e:370        raise WebSocketProxyException(str(e))371 372    if status != 200:373        raise WebSocketProxyException(f"failed CONNECT via proxy status: {status}")374 375    return sock376 377 378def read_headers(sock: socket.socket) -> tuple:379    status = None380    status_message = None381    headers: dict = {}382    trace("--- response header ---")383 384    while True:385        line = recv_line(sock)386        line = line.decode("utf-8").strip()387        if not line:388            break389        trace(line)390        if not status:391            status_info = line.split(" ", 2)392            status = int(status_info[1])393            if len(status_info) > 2:394                status_message = status_info[2]395        else:396            kv = line.split(":", 1)397            if len(kv) != 2:398                raise WebSocketException("Invalid header")399            key, value = kv400            if key.lower() == "set-cookie" and headers.get("set-cookie"):401                existing_cookie = headers.get("set-cookie")402                if existing_cookie is not None:403                    headers["set-cookie"] = existing_cookie + "; " + value.strip()404                else:405                    headers["set-cookie"] = value.strip()406            else:407                headers[key.lower()] = value.strip()408 409    trace("-----------------------")410 411    return status, headers, status_message412 
codekingpro/portable-devtools · Team Ai