Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
socks_proxy.py342 linesDownload Raw Back to _sync
1from __future__ import annotations2 3import logging4import ssl5 6import socksio7 8from .._backends.sync import SyncBackend9from .._backends.base import NetworkBackend, NetworkStream10from .._exceptions import ConnectionNotAvailable, ProxyError11from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url12from .._ssl import default_ssl_context13from .._synchronization import Lock14from .._trace import Trace15from .connection_pool import ConnectionPool16from .http11 import HTTP11Connection17from .interfaces import ConnectionInterface18 19logger = logging.getLogger("httpcore.socks")20 21 22AUTH_METHODS = {23    b"\x00": "NO AUTHENTICATION REQUIRED",24    b"\x01": "GSSAPI",25    b"\x02": "USERNAME/PASSWORD",26    b"\xff": "NO ACCEPTABLE METHODS",27}28 29REPLY_CODES = {30    b"\x00": "Succeeded",31    b"\x01": "General SOCKS server failure",32    b"\x02": "Connection not allowed by ruleset",33    b"\x03": "Network unreachable",34    b"\x04": "Host unreachable",35    b"\x05": "Connection refused",36    b"\x06": "TTL expired",37    b"\x07": "Command not supported",38    b"\x08": "Address type not supported",39}40 41 42def _init_socks5_connection(43    stream: NetworkStream,44    *,45    host: bytes,46    port: int,47    auth: tuple[bytes, bytes] | None = None,48) -> None:49    conn = socksio.socks5.SOCKS5Connection()50 51    # Auth method request52    auth_method = (53        socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED54        if auth is None55        else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD56    )57    conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))58    outgoing_bytes = conn.data_to_send()59    stream.write(outgoing_bytes)60 61    # Auth method response62    incoming_bytes = stream.read(max_bytes=4096)63    response = conn.receive_data(incoming_bytes)64    assert isinstance(response, socksio.socks5.SOCKS5AuthReply)65    if response.method != auth_method:66        requested = AUTH_METHODS.get(auth_method, "UNKNOWN")67        responded = AUTH_METHODS.get(response.method, "UNKNOWN")68        raise ProxyError(69            f"Requested {requested} from proxy server, but got {responded}."70        )71 72    if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD:73        # Username/password request74        assert auth is not None75        username, password = auth76        conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))77        outgoing_bytes = conn.data_to_send()78        stream.write(outgoing_bytes)79 80        # Username/password response81        incoming_bytes = stream.read(max_bytes=4096)82        response = conn.receive_data(incoming_bytes)83        assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)84        if not response.success:85            raise ProxyError("Invalid username/password")86 87    # Connect request88    conn.send(89        socksio.socks5.SOCKS5CommandRequest.from_address(90            socksio.socks5.SOCKS5Command.CONNECT, (host, port)91        )92    )93    outgoing_bytes = conn.data_to_send()94    stream.write(outgoing_bytes)95 96    # Connect response97    incoming_bytes = stream.read(max_bytes=4096)98    response = conn.receive_data(incoming_bytes)99    assert isinstance(response, socksio.socks5.SOCKS5Reply)100    if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:101        reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN")102        raise ProxyError(f"Proxy Server could not connect: {reply_code}.")103 104 105class SOCKSProxy(ConnectionPool):  # pragma: nocover106    """107    A connection pool that sends requests via an HTTP proxy.108    """109 110    def __init__(111        self,112        proxy_url: URL | bytes | str,113        proxy_auth: tuple[bytes | str, bytes | str] | None = None,114        ssl_context: ssl.SSLContext | None = None,115        max_connections: int | None = 10,116        max_keepalive_connections: int | None = None,117        keepalive_expiry: float | None = None,118        http1: bool = True,119        http2: bool = False,120        retries: int = 0,121        network_backend: NetworkBackend | None = None,122    ) -> None:123        """124        A connection pool for making HTTP requests.125 126        Parameters:127            proxy_url: The URL to use when connecting to the proxy server.128                For example `"http://127.0.0.1:8080/"`.129            ssl_context: An SSL context to use for verifying connections.130                If not specified, the default `httpcore.default_ssl_context()`131                will be used.132            max_connections: The maximum number of concurrent HTTP connections that133                the pool should allow. Any attempt to send a request on a pool that134                would exceed this amount will block until a connection is available.135            max_keepalive_connections: The maximum number of idle HTTP connections136                that will be maintained in the pool.137            keepalive_expiry: The duration in seconds that an idle HTTP connection138                may be maintained for before being expired from the pool.139            http1: A boolean indicating if HTTP/1.1 requests should be supported140                by the connection pool. Defaults to True.141            http2: A boolean indicating if HTTP/2 requests should be supported by142                the connection pool. Defaults to False.143            retries: The maximum number of retries when trying to establish144                a connection.145            local_address: Local address to connect from. Can also be used to146                connect using a particular address family. Using147                `local_address="0.0.0.0"` will connect using an `AF_INET` address148                (IPv4), while using `local_address="::"` will connect using an149                `AF_INET6` address (IPv6).150            uds: Path to a Unix Domain Socket to use instead of TCP sockets.151            network_backend: A backend instance to use for handling network I/O.152        """153        super().__init__(154            ssl_context=ssl_context,155            max_connections=max_connections,156            max_keepalive_connections=max_keepalive_connections,157            keepalive_expiry=keepalive_expiry,158            http1=http1,159            http2=http2,160            network_backend=network_backend,161            retries=retries,162        )163        self._ssl_context = ssl_context164        self._proxy_url = enforce_url(proxy_url, name="proxy_url")165        if proxy_auth is not None:166            username, password = proxy_auth167            username_bytes = enforce_bytes(username, name="proxy_auth")168            password_bytes = enforce_bytes(password, name="proxy_auth")169            self._proxy_auth: tuple[bytes, bytes] | None = (170                username_bytes,171                password_bytes,172            )173        else:174            self._proxy_auth = None175 176    def create_connection(self, origin: Origin) -> ConnectionInterface:177        return Socks5Connection(178            proxy_origin=self._proxy_url.origin,179            remote_origin=origin,180            proxy_auth=self._proxy_auth,181            ssl_context=self._ssl_context,182            keepalive_expiry=self._keepalive_expiry,183            http1=self._http1,184            http2=self._http2,185            network_backend=self._network_backend,186        )187 188 189class Socks5Connection(ConnectionInterface):190    def __init__(191        self,192        proxy_origin: Origin,193        remote_origin: Origin,194        proxy_auth: tuple[bytes, bytes] | None = None,195        ssl_context: ssl.SSLContext | None = None,196        keepalive_expiry: float | None = None,197        http1: bool = True,198        http2: bool = False,199        network_backend: NetworkBackend | None = None,200    ) -> None:201        self._proxy_origin = proxy_origin202        self._remote_origin = remote_origin203        self._proxy_auth = proxy_auth204        self._ssl_context = ssl_context205        self._keepalive_expiry = keepalive_expiry206        self._http1 = http1207        self._http2 = http2208 209        self._network_backend: NetworkBackend = (210            SyncBackend() if network_backend is None else network_backend211        )212        self._connect_lock = Lock()213        self._connection: ConnectionInterface | None = None214        self._connect_failed = False215 216    def handle_request(self, request: Request) -> Response:217        timeouts = request.extensions.get("timeout", {})218        sni_hostname = request.extensions.get("sni_hostname", None)219        timeout = timeouts.get("connect", None)220 221        with self._connect_lock:222            if self._connection is None:223                try:224                    # Connect to the proxy225                    kwargs = {226                        "host": self._proxy_origin.host.decode("ascii"),227                        "port": self._proxy_origin.port,228                        "timeout": timeout,229                    }230                    with Trace("connect_tcp", logger, request, kwargs) as trace:231                        stream = self._network_backend.connect_tcp(**kwargs)232                        trace.return_value = stream233 234                    # Connect to the remote host using socks5235                    kwargs = {236                        "stream": stream,237                        "host": self._remote_origin.host.decode("ascii"),238                        "port": self._remote_origin.port,239                        "auth": self._proxy_auth,240                    }241                    with Trace(242                        "setup_socks5_connection", logger, request, kwargs243                    ) as trace:244                        _init_socks5_connection(**kwargs)245                        trace.return_value = stream246 247                    # Upgrade the stream to SSL248                    if self._remote_origin.scheme == b"https":249                        ssl_context = (250                            default_ssl_context()251                            if self._ssl_context is None252                            else self._ssl_context253                        )254                        alpn_protocols = (255                            ["http/1.1", "h2"] if self._http2 else ["http/1.1"]256                        )257                        ssl_context.set_alpn_protocols(alpn_protocols)258 259                        kwargs = {260                            "ssl_context": ssl_context,261                            "server_hostname": sni_hostname262                            or self._remote_origin.host.decode("ascii"),263                            "timeout": timeout,264                        }265                        with Trace("start_tls", logger, request, kwargs) as trace:266                            stream = stream.start_tls(**kwargs)267                            trace.return_value = stream268 269                    # Determine if we should be using HTTP/1.1 or HTTP/2270                    ssl_object = stream.get_extra_info("ssl_object")271                    http2_negotiated = (272                        ssl_object is not None273                        and ssl_object.selected_alpn_protocol() == "h2"274                    )275 276                    # Create the HTTP/1.1 or HTTP/2 connection277                    if http2_negotiated or (278                        self._http2 and not self._http1279                    ):  # pragma: nocover280                        from .http2 import HTTP2Connection281 282                        self._connection = HTTP2Connection(283                            origin=self._remote_origin,284                            stream=stream,285                            keepalive_expiry=self._keepalive_expiry,286                        )287                    else:288                        self._connection = HTTP11Connection(289                            origin=self._remote_origin,290                            stream=stream,291                            keepalive_expiry=self._keepalive_expiry,292                        )293                except Exception as exc:294                    self._connect_failed = True295                    raise exc296            elif not self._connection.is_available():  # pragma: nocover297                raise ConnectionNotAvailable()298 299        return self._connection.handle_request(request)300 301    def can_handle_request(self, origin: Origin) -> bool:302        return origin == self._remote_origin303 304    def close(self) -> None:305        if self._connection is not None:306            self._connection.close()307 308    def is_available(self) -> bool:309        if self._connection is None:  # pragma: nocover310            # If HTTP/2 support is enabled, and the resulting connection could311            # end up as HTTP/2 then we should indicate the connection as being312            # available to service multiple requests.313            return (314                self._http2315                and (self._remote_origin.scheme == b"https" or not self._http1)316                and not self._connect_failed317            )318        return self._connection.is_available()319 320    def has_expired(self) -> bool:321        if self._connection is None:  # pragma: nocover322            return self._connect_failed323        return self._connection.has_expired()324 325    def is_idle(self) -> bool:326        if self._connection is None:  # pragma: nocover327            return self._connect_failed328        return self._connection.is_idle()329 330    def is_closed(self) -> bool:331        if self._connection is None:  # pragma: nocover332            return self._connect_failed333        return self._connection.is_closed()334 335    def info(self) -> str:336        if self._connection is None:  # pragma: nocover337            return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"338        return self._connection.info()339 340    def __repr__(self) -> str:341        return f"<{self.__class__.__name__} [{self.info()}]>"342 
codekingpro/portable-devtools · Team Ai